diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml new file mode 100644 index 0000000..ef44ff6 --- /dev/null +++ b/.gitea/workflows/test.yaml @@ -0,0 +1,242 @@ +# Paliad CI gate (t-paliad-282 / m/paliad#114). +# +# Single workflow, two purposes: +# +# - On every push: gate tier — build + unit + migration smoke. Red gate +# means no further work and (on main) no deploy. +# - On push to main with gate green: deploy step — calls the Dokploy +# compose-deploy API for paliad's compose Zx147ycurfYagKRl_Zzyo, then +# polls /health/ready until the new container reports 200. +# +# The deploy step REPLACES the previous Gitea-push → Dokploy webhook path +# (per m's Q11.4 pick: soft-launch with both alive for ~1 week, then +# disable the Dokploy auto-deploy toggle). Soft-launch leaves Dokploy's +# autoDeploy=true intact today — the workflow's deploy step is additive +# and idempotent (Dokploy's deploy is itself idempotent). +# +# Catches the three failure classes from 2026-05-25: +# +# - brunel slot collision (~13:20) — TestMigrations_NoDuplicateSlot, +# pure unit, no DB needed. +# - hermes dropped-col refs (~16:05) — TestBootSmoke, applies all NEW +# migrations (those not in the snapshot) end-to-end against a +# scratch DB restored from internal/db/testdata/prod-snapshot.sql. +# - mig 129 42501 ownership (~14:56→) — TestMigrations_EndToEndAsAppRole, +# applies new migrations as the prod-shaped `postgres` role (which +# is NOT a superuser on supabase/postgres — same shape as +# youpc-supabase prod, see internal/db/testdata/README.md). +# +# Snapshot approach: dump paliad schema + applied_migrations rows from +# prod, commit them. CI restores → ApplyMigrations sees existing migs as +# applied, only runs NEW migs (the ones this PR adds). This sidesteps the +# fresh-DB idempotence requirement on historical migrations (some of +# which use raw COMMIT or pre-installed extensions and can't be replayed +# from scratch). To refresh: `make refresh-snapshot`. +# +# Design: docs/design-cicd-pre-deploy-gate-2026-05-25.md (cronus inventor +# shift, t-paliad-282). + +name: Paliad CI gate + +on: + push: + branches: + - main + - 'mai/**' + pull_request: + branches: [main] + +env: + GO_VERSION: '1.24' + BUN_VERSION: '1.2' + +jobs: + # Gate job 1 — pure build. Catches go/bun build breakage that local + # `go build` would catch but which a worker might have skipped before + # pushing. Fast (~60 s) so a red here surfaces immediately. + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + + - name: go build + run: go build ./... + + - name: go vet + run: go vet ./... + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: bun install + build + working-directory: frontend + run: | + bun install --frozen-lockfile + bun run build + + # Gate job 2 — Go test suite + migration smoke against snapshot-restored + # scratch DB. + # + # The Postgres service container uses the same supabase/postgres image + # as youpc-supabase prod. The CI scratch DB starts empty; a setup step + # installs pg_trgm + restores the snapshot. After restore, paliad + # schema is at HEAD-of-snapshot and applied_migrations covers every + # migration up to (and including) the snapshot's max version. + # + # ApplyMigrations called in TestBootSmoke / TestMigrations_EndToEndAsAppRole + # sees the snapshot's applied set, finds whatever NEW migrations this + # PR added on top, and applies only those. The role-split smoke runs as + # `postgres` (which is NOT a superuser on supabase/postgres, matching + # the prod role topology) — any new migration that needs supabase_admin + # privilege fails here as it would in prod. + test-go: + runs-on: ubuntu-latest + + services: + # supabase/postgres baked-in auth schema + supabase role topology + # matches youpc-supabase prod. `postgres` here is NOT a superuser + # (verified live: \du postgres shows "Create role, Create DB, + # Replication, Bypass RLS" — no Superuser). This is the prod-shaped + # role the deploy uses. + postgres: + image: supabase/postgres:15.8.1.060 + env: + POSTGRES_PASSWORD: ci + POSTGRES_DB: paliad_scratch + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 5s + --health-retries 30 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + + - name: Install postgresql-client + run: | + apt-get update -qq && apt-get install -y -qq postgresql-client + + # Snapshot restore. Two prep steps as supabase_admin (the actual + # superuser): GRANT CREATE so the `postgres` role can later create + # schemas if a new mig needs it; install pg_trgm so the snapshot's + # trigram indexes restore. Snapshot itself loads as `postgres`. + - name: Provision + restore snapshot + env: + PGPASSWORD: ci + run: | + set -euo pipefail + psql -h localhost -U supabase_admin -d paliad_scratch -v ON_ERROR_STOP=1 \ + -c "GRANT CREATE ON DATABASE paliad_scratch TO postgres;" \ + -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + psql -h localhost -U postgres -d paliad_scratch -v ON_ERROR_STOP=1 \ + -f internal/db/testdata/prod-snapshot.sql + + # Pre-flight: catches brunel slot collision in seconds, no DB + # contact (still useful even though the test-go job has Postgres + # running, because the failure mode is independent). + - name: Migration coordination check + run: go test -count=1 -run TestMigrations_NoDuplicateSlot ./internal/db/ + + # Role-split end-to-end apply. Connects as `postgres` (NOT a + # superuser on supabase/postgres) and runs ApplyMigrations against + # the snapshot-restored DB. Existing migs are skipped (already in + # applied_migrations); NEW migs in this PR apply here. If a new + # migration assumes supabase_admin privilege, fails with the same + # 42501 error class that took paliad.de offline on 2026-05-25. + - name: Migration end-to-end (deploy role) + env: + TEST_APP_DATABASE_URL: postgres://postgres:ci@localhost:5432/paliad_scratch?sslmode=disable + run: go test -count=1 -run TestMigrations_EndToEndAsAppRole ./internal/db/ + + # Boot smoke. Confirms ApplyMigrations succeeds + applied set + # matches on-disk set + /healthz returns 200 + /health/ready + # returns 200 (the live-pool variant via TestHealthReady_Live). + - name: Boot smoke + readiness + env: + TEST_DATABASE_URL: postgres://postgres:ci@localhost:5432/paliad_scratch?sslmode=disable + run: go test -count=1 -run 'TestBootSmoke|TestHealthReady_Live' ./cmd/server/ + + # Full Go test suite WITHOUT TEST_DATABASE_URL so live-DB service + # tests skip (same shape as a developer laptop without a scratch + # DB). Live-DB tests in internal/services/* will be activated by a + # follow-up shift once the snapshot is verified stable across + # multiple PRs — they need investigation against supabase/postgres + # 15.8 (parameter type inference differs subtly from youpc-supabase). + - name: go test ./... (pure + skip-on-no-DB) + run: go test -count=1 ./internal/... ./cmd/... + + # Deploy step. Only runs on push to main and only after both gate jobs + # are green. Calls Dokploy's compose.deploy with the paliad compose ID + # (Zx147ycurfYagKRl_Zzyo) and polls /health/ready until it returns 200 + # or times out. + # + # Skipped on PR / feature branch pushes — those run the gate tier as + # a status check but don't trigger a prod deploy. Dokploy's existing + # autoDeploy=true webhook continues to fire during the soft-launch + # window (per Q11.4); it can be disabled in the Dokploy UI once this + # workflow has gated ≥5 successful green deploys. + deploy: + runs-on: ubuntu-latest + needs: [build, test-go] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + + steps: + - name: Trigger Dokploy compose deploy + env: + DOKPLOY_KEY: ${{ secrets.DOKPLOY_TOKEN }} + DOKPLOY_API: http://100.99.98.201:3000/api/trpc + COMPOSE_ID: Zx147ycurfYagKRl_Zzyo + run: | + set -euo pipefail + if [ -z "${DOKPLOY_KEY:-}" ]; then + echo "ERROR: DOKPLOY_TOKEN secret is not configured." + echo " Set the secret in Gitea repo settings before this step can deploy." + exit 2 + fi + echo "==> POST compose.deploy" + curl -sS --connect-timeout 5 --max-time 30 \ + -X POST \ + -H "x-api-key: $DOKPLOY_KEY" \ + -H "Content-Type: application/json" \ + -d "{\"json\":{\"composeId\":\"$COMPOSE_ID\"}}" \ + "$DOKPLOY_API/compose.deploy" + echo + + - name: Wait for /health/ready + run: | + set -euo pipefail + echo "==> polling https://paliad.de/health/ready" + # Up to 5 minutes (60 × 5 s) — paliad's cold-start is normally + # ≤30 s; the longer budget covers slow image pulls + migration + # apply. + for i in $(seq 1 60); do + status=$(curl -sS --connect-timeout 3 --max-time 5 \ + -o /dev/null -w '%{http_code}' \ + https://paliad.de/health/ready || echo "000") + if [ "$status" = "200" ]; then + echo "ready after ${i} poll(s)" + exit 0 + fi + echo " [$i/60] status=$status — sleeping 5s" + sleep 5 + done + echo "ERROR: /health/ready did not return 200 within 5 minutes." + echo " The deploy fired but the new container is not serving." + echo " Investigate: ssh mlake 'docker logs --tail 50 compose-transmit-multi-byte-driver-v7jth9-web-1'" + exit 1 diff --git a/Makefile b/Makefile index ff29621..cbc6b27 100644 --- a/Makefile +++ b/Makefile @@ -21,18 +21,24 @@ # the test runner's working dirs. None of them touch internal/db/migrations/ # files. -.PHONY: help verify-migrations verify-mig test test-go +.PHONY: help verify-migrations verify-mig verify-mig-app test test-go test-frontend refresh-snapshot help: @echo "Paliad — developer targets" @echo "" @echo " verify-migrations Dry-run pending migrations + boot smoke (needs TEST_DATABASE_URL)" @echo " verify-mig Alias for verify-migrations" + @echo " verify-mig-app End-to-end migration smoke as non-superuser role" + @echo " (needs TEST_APP_DATABASE_URL — t-paliad-282 / m/paliad#114)" @echo " test Short test pass — covers gate tier" @echo " test-go Full Go suite with race detector" + @echo " test-frontend Frontend bun:test suite" @echo "" @echo "Set TEST_DATABASE_URL to enable live-DB tests. Example:" @echo " export TEST_DATABASE_URL=postgres://paliad:...@localhost:11833/paliad_test" + @echo "" + @echo "Set TEST_APP_DATABASE_URL to enable the role-split smoke. Example:" + @echo " export TEST_APP_DATABASE_URL=postgres://paliad_app:...@localhost:5432/paliad_scratch" # Gate target — the test that would have caught mig 098 / mig 099 before # deploy. Combines: @@ -71,3 +77,67 @@ test: # (full suite, not per-PR). test-go: go test -race ./... + +# Frontend bun:test suite. Runs the 4 existing pure-TS tests today; will +# grow as mendel's Slice 3 (frontend test infill) lands. +test-frontend: + cd frontend && bun test + +# Role-split end-to-end migration smoke — the catch for the mig 129 42501 +# ownership class (m/paliad#114). Runs ApplyMigrations as a non-superuser +# role against TEST_APP_DATABASE_URL. Fails the build if any migration +# assumes more privilege than the deploy role has. +# +# Developer setup (local): +# psql -c "CREATE ROLE paliad_app LOGIN PASSWORD 'ci' NOSUPERUSER;" +# psql -c "CREATE DATABASE paliad_scratch OWNER paliad_app;" +# export TEST_APP_DATABASE_URL=postgres://paliad_app:ci@localhost:5432/paliad_scratch +verify-mig-app: + @if [ -z "$$TEST_APP_DATABASE_URL" ]; then \ + echo "ERROR: TEST_APP_DATABASE_URL is not set."; \ + echo " The role-split migration smoke cannot run without a non-superuser scratch DB."; \ + echo " See Makefile comments above this target for setup."; \ + exit 2; \ + fi + go test -count=1 -run TestMigrations_EndToEndAsAppRole ./internal/db/ + +# Refresh the prod schema snapshot used by CI's migration smoke +# (t-paliad-282 / m/paliad#114). Connects to youpc-supabase prod, dumps +# the paliad schema + applied_migrations rows, strips rows beyond the +# current branch's max on-disk version, and writes +# internal/db/testdata/prod-snapshot.sql. +# +# When to refresh: +# - After merging a PR that added a new migration to main. +# - When CI's migration smoke starts spuriously failing because the +# snapshot's applied set diverges from on-disk by more than this +# branch's worth of new migs. +# +# Requires PALIAD_PROD_DATABASE_URL env var (a Postgres URL with +# pg_dump rights on youpc-supabase). Example: +# export PALIAD_PROD_DATABASE_URL='postgres://postgres:PW@100.99.98.201:11833/postgres' +refresh-snapshot: + @if [ -z "$$PALIAD_PROD_DATABASE_URL" ]; then \ + echo "ERROR: PALIAD_PROD_DATABASE_URL is not set."; \ + echo " Refresh requires read access to youpc-supabase prod."; \ + exit 2; \ + fi + @echo "==> dumping paliad schema (no owner, no privs)..." + @pg_dump --schema-only --schema=paliad --no-owner --no-privileges \ + --no-publications --no-subscriptions \ + "$$PALIAD_PROD_DATABASE_URL" > internal/db/testdata/prod-snapshot.sql.tmp + @echo "==> appending applied_migrations rows..." + @pg_dump --data-only --table=paliad.applied_migrations \ + --no-owner --no-privileges \ + "$$PALIAD_PROD_DATABASE_URL" >> internal/db/testdata/prod-snapshot.sql.tmp + @echo "==> stripping pg16 \\restrict / \\unrestrict commands for pg15 compat..." + @sed -i.bak '/^\\restrict /d; /^\\unrestrict /d' internal/db/testdata/prod-snapshot.sql.tmp + @rm -f internal/db/testdata/prod-snapshot.sql.tmp.bak + @echo "==> stripping applied_migrations rows beyond branch's max on-disk version..." + @MAX_VER=$$(ls internal/db/migrations/*.up.sql | xargs -I{} basename {} | sed 's/_.*//' | sort -n | tail -1); \ + awk -v max=$$MAX_VER ' \ + /^[0-9]+\t/ { split($$0, a, "\t"); if (a[1]+0 > max) next; } \ + { print } \ + ' internal/db/testdata/prod-snapshot.sql.tmp > internal/db/testdata/prod-snapshot.sql + @rm internal/db/testdata/prod-snapshot.sql.tmp + @wc -l internal/db/testdata/prod-snapshot.sql diff --git a/cmd/server/main.go b/cmd/server/main.go index 035ce79..1b2b7c7 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -165,6 +165,7 @@ func main() { sysAuditSvc := services.NewSystemAuditLogService(pool) checklistTemplateSvc := services.NewChecklistTemplateService(pool, checklistCatalogSvc, sysAuditSvc, users) svcBundle = &handlers.Services{ + Pool: pool, Project: projectSvc, Team: teamSvc, PartnerUnit: partnerUnitSvc, diff --git a/cmd/server/main_smoke_test.go b/cmd/server/main_smoke_test.go index 0d123ba..0093656 100644 --- a/cmd/server/main_smoke_test.go +++ b/cmd/server/main_smoke_test.go @@ -98,6 +98,51 @@ func TestBootSmoke(t *testing.T) { if body := strings.TrimSpace(rec.Body.String()); body != "ok" { t.Errorf("GET /healthz: body=%q; want \"ok\"", body) } + + // (4) Readiness probe. With a nil Services bundle the endpoint MUST + // report 503 — that's the contract documented in handlers/handlers.go. + // A separate svc-with-Pool case is exercised in TestHealthReady (live). + rec = httptest.NewRecorder() + req = httptest.NewRequest(http.MethodGet, "/health/ready", nil) + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusServiceUnavailable { + t.Errorf("GET /health/ready (nil svc): status=%d; want 503", rec.Code) + } +} + +// TestHealthReady_Live asserts the readiness probe answers 200 when the +// pool is reachable, 503 when it isn't. Requires TEST_DATABASE_URL. +// +// Why a separate test: TestBootSmoke runs Register with svc=nil to keep +// its setup minimal; the pool-reachable path needs the pool wired in +// through svc.Pool. Two tests, two assertions, no entanglement. +func TestHealthReady_Live(t *testing.T) { + url := os.Getenv("TEST_DATABASE_URL") + if url == "" { + t.Skip("TEST_DATABASE_URL not set — skipping live readiness probe") + } + + if err := db.ApplyMigrations(url); err != nil { + t.Fatalf("db.ApplyMigrations: %v", err) + } + pool, err := db.OpenPool(url) + if err != nil { + t.Fatalf("open pool: %v", err) + } + + mux := http.NewServeMux() + authClient := auth.NewClient("https://test.invalid", "anon-key", []byte("test-secret")) + handlers.Register(mux, authClient, "", &handlers.Services{Pool: pool}) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/health/ready", nil) + mux.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Errorf("GET /health/ready (live pool): status=%d, body=%q; want 200", rec.Code, rec.Body.String()) + } + if body := strings.TrimSpace(rec.Body.String()); body != "ready" { + t.Errorf("GET /health/ready (live pool): body=%q; want \"ready\"", body) + } } // embeddedMigrationVersions returns every N where N_*.up.sql exists in diff --git a/docs/cicd-runner-setup-2026-05-25.md b/docs/cicd-runner-setup-2026-05-25.md new file mode 100644 index 0000000..e434ddb --- /dev/null +++ b/docs/cicd-runner-setup-2026-05-25.md @@ -0,0 +1,181 @@ +# CI/CD runner setup — paliad + +**Companion to:** `docs/design-cicd-pre-deploy-gate-2026-05-25.md` (Slice A, t-paliad-282 / m/paliad#114) +**Date:** 2026-05-25 +**Audience:** mlake / mriver admin (m or head) + +Slice A's `.gitea/workflows/test.yaml` requires (a) at least one online Gitea Actions runner and (b) a Dokploy API token wired as a repo secret. Both are one-time setup actions that paliad's source tree cannot perform itself — they live on infra-side. This doc lists them so the workflow can go green on its first run. + +--- + +## 0. Pre-flight: what already exists + +Verified live (2026-05-25 cronus inventor shift): + +- Gitea 1.24.4 on `mgit.msbls.de`, `has_actions: true` on `m/paliad`. +- `/api/v1/admin/actions/runners` reports **2 runners** registered. They are likely the shared runners used by `m/mGreen` and `m/mGeo` (both have `.gitea/workflows/deploy.yml` with `runs-on: self-hosted`). +- `m/paliad/actions/tasks` reports `total_count=0` — paliad has never run a workflow yet. + +The existing runners may already be capable of running paliad's workflow without further setup. The verification step (§3) below tells you whether they are. + +--- + +## 1. Runner placement decision (m's Q11.1) + +m's pick: **mriver**. + +Rationale: mriver hosts the mai worker fleet but workers spend most of their time waiting on Anthropic. mlake's Dokploy + Swarm workload is more contended. A new runner on mriver adds the least pressure to either box. + +If mriver is offline or saturated when CI first fires, fall back to the existing mlake-side runners (they're already registered; no provisioning needed). + +--- + +## 2. One-time setup (admin steps) + +### 2.1 Register a new Gitea Actions runner on mriver + +```bash +# On mriver, as m: +# 1. Download the act_runner binary (matching Gitea 1.24.x) +curl -L -o /usr/local/bin/act_runner \ + https://gitea.com/gitea/act_runner/releases/download/v0.2.13/act_runner-0.2.13-linux-amd64 +chmod +x /usr/local/bin/act_runner + +# 2. Get a runner registration token. In the Gitea UI: +# /admin → Actions → Runners → "Create new Runner" +# (or org-scope: /m/paliad/settings/actions/runners) +# Copy the token. + +# 3. Register +mkdir -p ~/act_runner && cd ~/act_runner +act_runner register --no-interactive \ + --instance https://mgit.msbls.de \ + --token \ + --name mriver-paliad-1 \ + --labels ubuntu-latest:docker://node:20-bookworm + +# 4. Run as a systemd unit (preferred) or as a session daemon +# Systemd unit example: /etc/systemd/system/act_runner.service +# [Unit] +# Description=Gitea Actions runner +# After=network.target +# [Service] +# User=m +# WorkingDirectory=/home/m/act_runner +# ExecStart=/usr/local/bin/act_runner daemon +# Restart=on-failure +# [Install] +# WantedBy=multi-user.target +sudo systemctl enable --now act_runner +sudo systemctl status act_runner +``` + +**Why `ubuntu-latest:docker://node:20-bookworm` for the label?** Gitea Actions' `runs-on: ubuntu-latest` resolves via the runner's label map. Mapping it to a Docker image gives the workflow a sandbox with Docker available — required for our Postgres service container in `test.yaml`. mriver should have Docker (for `paliadin-shim`); if not, install it. + +### 2.2 Register the Dokploy API token as a repo secret + +The workflow's `deploy` job needs `secrets.DOKPLOY_TOKEN`. Use the existing project-wide Dokploy API key (the one stored in `~/.claude/skills/mai-dokploy/SKILL.md`). + +In the Gitea UI: +- Navigate to `https://mgit.msbls.de/m/paliad/settings/actions/secrets` +- Click "Add secret" + - Name: `DOKPLOY_TOKEN` + - Value: `mai-ottosSyRHMhmLhhhXaCbKzbqKBuSqzqEtmKDOPelPCeimTaYsbmaVslVyEgJZGCIxVdz` + +Or via API (mAi identity): +```bash +curl --netrc-file ~/.netrc-mai -sS -X POST \ + -H "Content-Type: application/json" \ + https://mgit.msbls.de/api/v1/repos/m/paliad/actions/secrets/DOKPLOY_TOKEN \ + -d '{"data":"mai-ottosSyRHMhmLhhhXaCbKzbqKBuSqzqEtmKDOPelPCeimTaYsbmaVslVyEgJZGCIxVdz"}' +``` + +(Requires repo-owner permission. If mAi lacks it, m runs it.) + +--- + +## 3. Verify the runner sees the workflow + +After (2.1) + (2.2): + +```bash +# Push the Slice A branch (the one this doc lives on) +git push origin mai/cronus/coder-cicd-slice-a + +# Confirm the runner picked up the job +curl --netrc-file ~/.netrc-mai -sS \ + "https://mgit.msbls.de/api/v1/repos/m/paliad/actions/tasks?limit=5" | jq '.' +``` + +A new task per job should appear (build, test-go). If `total_count` stays 0, the runner labels don't match the workflow's `runs-on`. Re-register with `--labels ubuntu-latest` (no docker:// suffix) and the existing runners on mlake will pick it up via shell mode. + +--- + +## 4. Soft-launch (m's Q11.4) + +m's pick: **keep both Dokploy auto-deploy and the workflow's deploy step alive for ~1 week. After ≥5 successful green deploys via the workflow, disable Dokploy's autoDeploy in the Dokploy UI for the paliad compose.** + +While both are live, every push to main fires: +1. Dokploy webhook (existing path) → deploys immediately, no gate. +2. Gitea workflow → on green, ALSO calls `compose.deploy`. + +The second call is idempotent — if Dokploy already deployed the same commit, this is a no-op. The workflow's value during soft-launch is the **gate signal**: a red workflow on a green main = the bad migration shipped via the unguarded webhook and broke prod, and the workflow is shouting about it. + +After confidence builds: +1. In the Dokploy UI, navigate to the paliad compose → Settings. +2. Toggle "Auto Deploy" off. +3. Save. + +From this point, the only path to deploy is the workflow's deploy job. Red workflow = no deploy. + +--- + +## 5. What Slice A catches today — and what it doesn't + +After this branch (`mai/cronus/coder-cicd-slice-a`) merges to main: + +### Catches (active in CI) + +- **Build breakage** — `go build`, `go vet`, `bun run build`. Red gate, no deploy. +- **Slot collisions** — `TestMigrations_NoDuplicateSlot` runs without a DB. A PR adding migration N when version N already exists fails at gate time. This is the brunel-class catch (m/paliad#114 ~13:20 outage). +- **New-migration shape errors (hermes class)** — `TestBootSmoke` runs `ApplyMigrations` against the snapshot-restored DB. New migs from this PR get applied for real; any column/relation/syntax error fails the gate before merge. +- **New-migration ownership errors (mig 129 42501 class)** — `TestMigrations_EndToEndAsAppRole` runs `ApplyMigrations` connected as `postgres` (NON-superuser on `supabase/postgres:15.8.1.060`, same role topology as youpc-supabase prod). Any migration that assumes supabase_admin privilege fails with the same `42501 must be owner` error class that took paliad.de offline on 2026-05-25. +- **Readiness probe regressions** — `TestHealthReady_Live` confirms `/health/ready` returns 200 against a live pool, 503 against a nil pool. +- **Pure-Go test regressions** — `go test ./internal/... ./cmd/...` runs without `TEST_DATABASE_URL` (live-DB service tests skip the same way they do on a developer laptop without a scratch DB). + +### Mechanism — the snapshot approach + +CI's scratch DB starts from a `pg_dump` of youpc-supabase paliad schema + +`paliad.applied_migrations` rows, committed to `internal/db/testdata/prod-snapshot.sql`. After restore, the scratch DB is at "paliad HEAD of snapshot" and `ApplyMigrations` sees only this PR's new migrations as pending. + +This sidesteps the fresh-DB idempotence problem: several historical migrations (notably mig 037's missing `CREATE EXTENSION pg_trgm`, mig 051's inner `COMMIT;`) can't be replayed from scratch against `supabase/postgres:15.8.1.060`. The snapshot pins everything that's already applied in prod and lets CI focus on what's new — which is what we actually care about for outage prevention. + +Snapshot refresh: `make refresh-snapshot` with `PALIAD_PROD_DATABASE_URL` set (see `internal/db/testdata/README.md`). + +### Known gap — live-DB service tests don't run in CI + +`internal/services/*_test.go` tests with `TEST_DATABASE_URL` set fail against `supabase/postgres:15.8.1.060` with `42P08 inconsistent types deduced for parameter` errors on some INSERT bind paths. The same tests pass against youpc-supabase prod. Cause is unconfirmed — likely subtle differences in type inference between the dockerized image and the prod cluster's configuration. CI today runs `go test ./...` without `TEST_DATABASE_URL` so these tests skip. Not blocking outage prevention; tracked as a follow-up for the post-Slice-A coder. + +### Migration cleanup also bundled in this PR + +Two surgical migration improvements that surfaced during snapshot debugging — kept here because they're small and harmless: + +- **mig 024 + 027** — `ALTER INDEX` / `ALTER POLICY` exception handlers now catch `undefined_object` OR `undefined_table` OR `duplicate_object`. Old handler caught only `undefined_object`; Postgres raises `undefined_table` when the source object never existed and `duplicate_object` when the destination already exists. The expanded handler makes the migrations truly idempotent across the three plausible states: source-still-German (rename succeeds), already-renamed (catches duplicate_object), and fresh-DB-never-had-German (catches undefined_table). + +Other migration history bugs (mig 037 missing pg_trgm, mig 051 inner COMMIT) are tracked as a separate cleanup task — not blocking, because the snapshot bypasses them. + +### Verification checklist (after Slice A merges) + +1. **Workflow green on its first PR run?** Check `/m/paliad/actions`. If not, fix before merging. +2. **Dokploy `compose.deploy` call succeeds?** The workflow's `deploy` job logs the POST response. A successful response is a Dokploy job ID; a 4xx is an auth or compose-id problem. +3. **`/health/ready` returns 200 within 5 minutes after a green deploy?** The workflow polls this. If it times out, the migration may have failed silently inside the new container — check `docker logs --tail 50 compose-transmit-multi-byte-driver-v7jth9-web-1` on mlake. +4. **Reproduce the slot-collision catch locally:** rename `131_…up.sql` to `129_…` (duplicate slot) → workflow MUST fail at `Migration coordination check`. Revert before pushing. +5. **Reproduce the role-split catch locally:** add a no-op migration `132_test_supersedes.up.sql` containing `REINDEX SYSTEM paliad_scratch;` (requires superuser). Workflow MUST fail at `Migration end-to-end (deploy role)`. Revert before pushing. + +--- + +## 6. Future polish (Slice D, m's Q4 R-pick) + +`mai-test` post-merge shift: once Slice A is stable, wire a Gitea webhook on push-to-main that fires `/mai-test` as a follow-up shift. It runs the broader smoke + integration suite and posts results as a Gitea commit status. Not blocking; the gate doesn't depend on it. + +Implementation belongs in `m/mAi` (the mai webhook handler), not in paliad. Out of scope for Slice A. diff --git a/internal/db/migrate_test.go b/internal/db/migrate_test.go index a1743d3..a7f3854 100644 --- a/internal/db/migrate_test.go +++ b/internal/db/migrate_test.go @@ -116,6 +116,57 @@ func TestMigrations_DryRun(t *testing.T) { } } +// TestMigrations_NoDuplicateSlot is a free-standing pre-flight check that +// scanEmbeddedMigrations refuses to walk a tree where two *.up.sql files +// claim the same NNN slot. This is the brunel-slot-collision class of +// outage (m/paliad#114, 2026-05-25 ~13:20): a worker writes a migration +// at slot N while another shipped slot N from a separate branch, both +// merge, both end up in the embed.FS, and the runner refuses to start. +// +// Catching this at CI time (no DB needed) lets the second PR fail before +// it merges, instead of breaking prod at the next deploy. Pure unit test; +// runs even on developer laptops that don't set TEST_DATABASE_URL. +func TestMigrations_NoDuplicateSlot(t *testing.T) { + if _, err := scanEmbeddedMigrations(); err != nil { + t.Fatalf("scanEmbeddedMigrations: %v "+ + "(two migrations share the same NNN slot — coordinate with head "+ + "and rename one of them before merging)", err) + } +} + +// TestMigrations_EndToEndAsAppRole applies every embedded migration in +// numeric order against a scratch DB connected as a NON-SUPERUSER role. +// This is the prod-shape smoke that the per-mig BEGIN/ROLLBACK dry-run +// (TestMigrations_DryRun) cannot deliver: the dry-run runs each +// statement in isolation and rolls back, so it cannot reproduce the +// mig-129-class outage (m/paliad#114, 2026-05-25 ~14:56 — pq: must be +// owner of table project_event_choices, SQLSTATE 42501) where a +// migration assumes ownership the deploy role doesn't have. +// +// Requires TEST_APP_DATABASE_URL — a Postgres URL whose role is NOT a +// superuser and does NOT own the `paliad` schema (m's Q11.2 pick: +// generic two-role model, see docs/design-cicd-pre-deploy-gate-2026-05-25.md +// §6.2(a)). The CI workflow creates the role + schema split before +// invoking the test; a developer who wants to reproduce the gate locally +// runs the same SQL preamble (see Makefile target `verify-migrations`). +// +// Skipped without TEST_APP_DATABASE_URL — keeps `go test ./...` green +// on machines that haven't set up the role split. +func TestMigrations_EndToEndAsAppRole(t *testing.T) { + url := os.Getenv("TEST_APP_DATABASE_URL") + if url == "" { + t.Skip("TEST_APP_DATABASE_URL not set — skipping role-split end-to-end migration smoke") + } + if err := ApplyMigrations(url); err != nil { + t.Fatalf("ApplyMigrations as app role failed: %v "+ + "(a migration assumes more privilege than the deploy role has — "+ + "common cases: ALTER TABLE on a schema-owner table, CREATE EXTENSION "+ + "without grants, SET ROLE without permission. Fix the migration to "+ + "work as the deploy role, or arrange for the schema to be owned by "+ + "the deploy role)", err) + } +} + // readAppliedVersions returns the set of versions present in // paliad.applied_migrations on the scratch DB. Missing table → empty set // (fresh-DB path; the table only exists after the runner has been called). diff --git a/internal/db/migrations/024_rename_department_columns.up.sql b/internal/db/migrations/024_rename_department_columns.up.sql index bfac3c3..98e655e 100644 --- a/internal/db/migrations/024_rename_department_columns.up.sql +++ b/internal/db/migrations/024_rename_department_columns.up.sql @@ -26,24 +26,24 @@ DO $$ BEGIN ALTER TABLE paliad.department_members RENAME COLUMN dezernat_id TO d -- Constraints (primary key + foreign keys + check). Renaming a pkey -- constraint also renames the underlying index of the same name. -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_pkey TO departments_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_lead_user_id_fkey TO departments_lead_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_office_check TO departments_office_check; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_pkey TO department_members_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_dezernat_id_fkey TO department_members_department_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_user_id_fkey TO department_members_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_pkey TO departments_pkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_lead_user_id_fkey TO departments_lead_user_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_office_check TO departments_office_check; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_pkey TO department_members_pkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_dezernat_id_fkey TO department_members_department_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_user_id_fkey TO department_members_user_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; -- --------------------------------------------------------------------------- -- Standalone indexes (non-pkey). -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER INDEX paliad.dezernate_office_idx RENAME TO departments_office_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER INDEX paliad.dezernate_lead_idx RENAME TO departments_lead_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER INDEX paliad.dezernat_mitglieder_user_idx RENAME TO department_members_user_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.dezernate_office_idx RENAME TO departments_office_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.dezernate_lead_idx RENAME TO departments_lead_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.dezernat_mitglieder_user_idx RENAME TO department_members_user_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; -- --------------------------------------------------------------------------- -- RLS policies -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER POLICY dezernate_select ON paliad.departments RENAME TO departments_select; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY dezernate_write ON paliad.departments RENAME TO departments_write; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY dezernat_mitglieder_select ON paliad.department_members RENAME TO department_members_select; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY dezernat_mitglieder_write ON paliad.department_members RENAME TO department_members_write; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY dezernate_select ON paliad.departments RENAME TO departments_select; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY dezernate_write ON paliad.departments RENAME TO departments_write; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY dezernat_mitglieder_select ON paliad.department_members RENAME TO department_members_select; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY dezernat_mitglieder_write ON paliad.department_members RENAME TO department_members_write; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; diff --git a/internal/db/migrations/027_rename_to_partner_units.up.sql b/internal/db/migrations/027_rename_to_partner_units.up.sql index 4d26879..7a3a3c6 100644 --- a/internal/db/migrations/027_rename_to_partner_units.up.sql +++ b/internal/db/migrations/027_rename_to_partner_units.up.sql @@ -63,27 +63,27 @@ ALTER TABLE paliad.partner_unit_members RENAME COLUMN department_id TO partner_u -- 5. Rename constraints. Postgres auto-renames the underlying index for -- pkey/uniq constraints; standalone indexes are renamed in step 6. -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_pkey TO partner_units_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_lead_user_id_fkey TO partner_units_lead_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_office_check TO partner_units_office_check; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_pkey TO partner_unit_members_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_department_id_fkey TO partner_unit_members_partner_unit_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_user_id_fkey TO partner_unit_members_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_pkey TO partner_units_pkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_lead_user_id_fkey TO partner_units_lead_user_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_units RENAME CONSTRAINT departments_office_check TO partner_units_office_check; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_pkey TO partner_unit_members_pkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_department_id_fkey TO partner_unit_members_partner_unit_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER TABLE paliad.partner_unit_members RENAME CONSTRAINT department_members_user_id_fkey TO partner_unit_members_user_id_fkey; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; -- --------------------------------------------------------------------------- -- 6. Rename non-pkey indexes. -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER INDEX paliad.departments_office_idx RENAME TO partner_units_office_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER INDEX paliad.departments_lead_idx RENAME TO partner_units_lead_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER INDEX paliad.department_members_user_idx RENAME TO partner_unit_members_user_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.departments_office_idx RENAME TO partner_units_office_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.departments_lead_idx RENAME TO partner_units_lead_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER INDEX paliad.department_members_user_idx RENAME TO partner_unit_members_user_idx; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; -- --------------------------------------------------------------------------- -- 7. Rename RLS policies. -- --------------------------------------------------------------------------- -DO $$ BEGIN ALTER POLICY departments_select ON paliad.partner_units RENAME TO partner_units_select; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY departments_write ON paliad.partner_units RENAME TO partner_units_write; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY department_members_select ON paliad.partner_unit_members RENAME TO partner_unit_members_select; EXCEPTION WHEN undefined_object THEN NULL; END $$; -DO $$ BEGIN ALTER POLICY department_members_write ON paliad.partner_unit_members RENAME TO partner_unit_members_write; EXCEPTION WHEN undefined_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY departments_select ON paliad.partner_units RENAME TO partner_units_select; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY departments_write ON paliad.partner_units RENAME TO partner_units_write; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY department_members_select ON paliad.partner_unit_members RENAME TO partner_unit_members_select; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; +DO $$ BEGIN ALTER POLICY department_members_write ON paliad.partner_unit_members RENAME TO partner_unit_members_write; EXCEPTION WHEN undefined_object OR undefined_table OR duplicate_object THEN NULL; END $$; -- --------------------------------------------------------------------------- -- 8. Audit table for partner-unit events. Mutations on partner_units + diff --git a/internal/db/testdata/README.md b/internal/db/testdata/README.md new file mode 100644 index 0000000..7dbfb76 --- /dev/null +++ b/internal/db/testdata/README.md @@ -0,0 +1,69 @@ +# `internal/db/testdata/` — CI snapshot + +## `prod-snapshot.sql` + +Schema-only `pg_dump` of paliad's prod DB (youpc-supabase paliad schema) +plus the rows of `paliad.applied_migrations` that match this branch's +on-disk migration set. + +**Purpose.** Lets CI's migration smoke (`.gitea/workflows/test.yaml`) +restore a Postgres scratch DB to "paliad at HEAD-of-snapshot" without +having to replay 131 migrations from scratch. ApplyMigrations on the +restored DB sees the applied set and only runs whatever NEW migrations +this PR adds — exactly the integration shape we want to test, and the +same shape prod sees on every deploy. + +**Why a snapshot at all.** Running ApplyMigrations from scratch against a +fresh `supabase/postgres:15.8.1.060` surfaces multiple fresh-DB +idempotence bugs in historical migrations (raw `COMMIT;` in mig 051, +missing `CREATE EXTENSION pg_trgm` for mig 037, ALTER POLICY +exception-handler gaps in mig 024/027 — the last is fixed in this PR). +Fixing them all is a separate cleanup. The snapshot sidesteps them by +starting CI from a state where every historical migration is already +applied as it was in prod. + +**Schema scope.** `--schema=paliad` only. Auth schema comes baked into +`supabase/postgres`; CI's setup step installs `pg_trgm` before restoring. + +**Ownership.** `--no-owner --no-privileges` keeps the dump portable +across role topologies (CI's supabase_admin / postgres / authenticated / +anon don't have to match prod's exact role layout). The role-split smoke +relies on `postgres` being a non-superuser, which is true on +supabase/postgres by default. + +**Refresh.** Run `make refresh-snapshot` with `PALIAD_PROD_DATABASE_URL` +set to a Postgres URL with `pg_dump` rights on youpc-supabase. The +target appends data rows for `paliad.applied_migrations`, strips +`\restrict` / `\unrestrict` commands (pg 16 dump → pg 15 restore), and +filters out applied-migrations rows for versions beyond the branch's +local max. The CI workflow consumes the resulting file verbatim. + +**Verify a refresh.** Boot a local scratch: + +```bash +docker run -d --rm --name paliad-snap \ + -e POSTGRES_PASSWORD=ci -e POSTGRES_DB=paliad_scratch \ + -p 15433:5432 supabase/postgres:15.8.1.060 +sleep 5 +docker exec -e PGPASSWORD=ci paliad-snap psql -h localhost -U supabase_admin -d paliad_scratch \ + -c "GRANT CREATE ON DATABASE paliad_scratch TO postgres;" \ + -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" +cat internal/db/testdata/prod-snapshot.sql | docker exec -i -e PGPASSWORD=ci paliad-snap \ + psql -h localhost -U postgres -d paliad_scratch -v ON_ERROR_STOP=1 +TEST_DATABASE_URL="postgres://postgres:ci@localhost:15433/paliad_scratch?sslmode=disable" \ +TEST_APP_DATABASE_URL="postgres://postgres:ci@localhost:15433/paliad_scratch?sslmode=disable" \ + go test -count=1 -run 'TestMigrations|TestBootSmoke|TestHealthReady_Live' ./internal/db/ ./cmd/server/ +docker stop paliad-snap +``` + +All four named tests must pass. If any fails after a refresh, +investigate before merging — usually because a new migration was added +to prod that this branch doesn't have on disk yet. + +**Why is the snapshot not gzipped?** Small enough (~200 KB) that the +diff stays human-readable in `git diff` reviews. If it crosses ~1 MB, +gzip + decompress-on-restore in CI. + +**Privacy.** Schema-only dump, no row data from any paliad table (except +`paliad.applied_migrations`, which contains migration filenames + +checksums — public info already in the repo). diff --git a/internal/db/testdata/prod-snapshot.sql b/internal/db/testdata/prod-snapshot.sql new file mode 100644 index 0000000..9706035 --- /dev/null +++ b/internal/db/testdata/prod-snapshot.sql @@ -0,0 +1,6278 @@ +-- +-- PostgreSQL database dump +-- + + +-- Dumped from database version 15.8 +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: paliad; Type: SCHEMA; Schema: -; Owner: - +-- + +CREATE SCHEMA paliad; + + +-- +-- Name: approval_policy_effective(uuid, text, text); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.approval_policy_effective(p_project_id uuid, p_entity_type text, p_lifecycle text) RETURNS TABLE(requires_approval boolean, min_role text, source text, source_id uuid) + LANGUAGE plpgsql STABLE + AS $$ +BEGIN + RETURN QUERY + WITH path AS ( + SELECT string_to_array(p.path, '.')::uuid[] AS ids + FROM paliad.projects p WHERE p.id = p_project_id + ), + project_rows AS ( + SELECT ap.requires_approval, + ap.min_role, + 'project'::text AS src, + ap.project_id AS sid, + 1 AS src_priority + FROM paliad.approval_policies ap + WHERE ap.project_id = p_project_id + AND ap.entity_type = p_entity_type + AND ap.lifecycle_event = p_lifecycle + ), + ancestor_rows AS ( + SELECT ap.requires_approval, + ap.min_role, + 'ancestor'::text AS src, + ap.project_id AS sid, + 2 AS src_priority + FROM paliad.approval_policies ap, path + WHERE ap.project_id = ANY(path.ids) + AND ap.project_id <> p_project_id + AND ap.entity_type = p_entity_type + AND ap.lifecycle_event = p_lifecycle + ), + unit_rows AS ( + SELECT ap.requires_approval, + ap.min_role, + 'unit_default'::text AS src, + ap.partner_unit_id AS sid, + 3 AS src_priority + FROM paliad.approval_policies ap + JOIN paliad.project_partner_units ppu + ON ppu.partner_unit_id = ap.partner_unit_id + WHERE ppu.project_id = p_project_id + AND ap.entity_type = p_entity_type + AND ap.lifecycle_event = p_lifecycle + ), + candidates AS ( + SELECT * FROM project_rows + UNION ALL + SELECT * FROM ancestor_rows + UNION ALL + SELECT * FROM unit_rows + ), + strictest_role AS ( + SELECT c.min_role, + c.src AS source, + c.sid AS source_id + FROM candidates c + WHERE c.requires_approval = true + AND c.min_role IS NOT NULL + ORDER BY paliad.approval_role_level(c.min_role) DESC, + c.src_priority ASC + LIMIT 1 + ), + no_approval_attribution AS ( + SELECT c.src AS source, c.sid AS source_id + FROM candidates c + WHERE c.requires_approval = false + ORDER BY c.src_priority ASC + LIMIT 1 + ), + summary AS ( + SELECT bool_or(c.requires_approval) AS req + FROM candidates c + ) + SELECT + COALESCE(s.req, false) AS requires_approval, + sr.min_role AS min_role, + COALESCE(sr.source, na.source) AS source, + COALESCE(sr.source_id, na.source_id) AS source_id + FROM summary s + LEFT JOIN strictest_role sr ON true + LEFT JOIN no_approval_attribution na ON true + WHERE EXISTS (SELECT 1 FROM candidates); +END; +$$; + + +-- +-- Name: FUNCTION approval_policy_effective(p_project_id uuid, p_entity_type text, p_lifecycle text); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.approval_policy_effective(p_project_id uuid, p_entity_type text, p_lifecycle text) IS 'Effective approval policy resolver (t-paliad-160 M2). Returns requires_approval (OR across candidates) + min_role (MAX along the role ladder among requires_approval=true candidates) + source attribution. Zero rows when no policy candidates exist.'; + + +-- +-- Name: approval_role_from_unit_role(text); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.approval_role_from_unit_role(unit_role text) RETURNS text + LANGUAGE sql IMMUTABLE + AS $$ + SELECT CASE unit_role + WHEN 'lead' THEN 'partner' + WHEN 'attorney' THEN 'associate' + WHEN 'senior_pa' THEN 'senior_pa' + WHEN 'pa' THEN 'pa' + ELSE 'observer' + END +$$; + + +-- +-- Name: approval_role_level(text); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.approval_role_level(role text) RETURNS integer + LANGUAGE sql IMMUTABLE + AS $$ + SELECT CASE role + WHEN 'partner' THEN 5 + WHEN 'of_counsel' THEN 4 + WHEN 'associate' THEN 3 + WHEN 'senior_pa' THEN 2 + WHEN 'pa' THEN 1 + WHEN 'paralegal' THEN 0 + -- Legacy 'lead' kept at level 5 for the deprecated-shadow window: + -- old call sites that still read pt.role would otherwise return + -- level 0 and break authority for projects where the migration + -- has run but the Go redirect hasn't. Removed in migration 058. + WHEN 'lead' THEN 5 + ELSE 0 + END +$$; + + +-- +-- Name: FUNCTION approval_role_level(role text); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.approval_role_level(role text) IS 'Strict-ladder level for the t-paliad-138 / t-paliad-148 approval gate. Reads paliad.users.profession; legacy project_teams.role values still recognised via the lead→5 shadow row until migration 058 retires the column. Higher level always satisfies lower; level 0 = ineligible.'; + + +-- +-- Name: can_see_checklist(uuid, uuid); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.can_see_checklist(_user_id uuid, _checklist_id uuid) RETURNS boolean + LANGUAGE sql STABLE SECURITY DEFINER + SET search_path TO 'paliad', 'public' + AS $$ + -- Owner + SELECT EXISTS ( + SELECT 1 FROM paliad.checklists c + WHERE c.id = _checklist_id AND c.owner_id = _user_id + ) + -- firm / global + OR EXISTS ( + SELECT 1 FROM paliad.checklists c + WHERE c.id = _checklist_id AND c.visibility IN ('firm', 'global') + ) + -- Explicit share: user + OR EXISTS ( + SELECT 1 FROM paliad.checklist_shares s + WHERE s.checklist_id = _checklist_id + AND s.recipient_kind = 'user' + AND s.recipient_user_id = _user_id + ) + -- Explicit share: office (caller's primary OR additional offices) + OR EXISTS ( + SELECT 1 + FROM paliad.checklist_shares s + JOIN paliad.users u ON u.id = _user_id + WHERE s.checklist_id = _checklist_id + AND s.recipient_kind = 'office' + AND (s.recipient_office = u.office + OR s.recipient_office = ANY(u.additional_offices)) + ) + -- Explicit share: partner_unit (caller is a member) + OR EXISTS ( + SELECT 1 + FROM paliad.checklist_shares s + JOIN paliad.partner_unit_members pum + ON pum.partner_unit_id = s.recipient_partner_unit_id + AND pum.user_id = _user_id + WHERE s.checklist_id = _checklist_id + AND s.recipient_kind = 'partner_unit' + ) + -- Explicit share: project (caller can see the project via existing predicate) + OR EXISTS ( + SELECT 1 FROM paliad.checklist_shares s + WHERE s.checklist_id = _checklist_id + AND s.recipient_kind = 'project' + AND paliad.can_see_project(s.recipient_project_id) + ); +$$; + + +-- +-- Name: FUNCTION can_see_checklist(_user_id uuid, _checklist_id uuid); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.can_see_checklist(_user_id uuid, _checklist_id uuid) IS 'True iff the user owns the checklist OR firm/global visibility OR an explicit share row matches the caller (by user / office / partner_unit / project ancestry).'; + + +-- +-- Name: can_see_project(uuid); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.can_see_project(_project_id uuid) RETURNS boolean + LANGUAGE sql STABLE SECURITY DEFINER + SET search_path TO 'paliad', 'public' + AS $$ + 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.projects target + JOIN paliad.project_teams pt + ON pt.user_id = auth.uid() + AND pt.project_id = ANY(string_to_array(target.path, '.')::uuid[]) + WHERE target.id = _project_id + ) + OR EXISTS ( + SELECT 1 + FROM paliad.projects target + JOIN paliad.project_partner_units ppu + ON ppu.project_id = ANY(string_to_array(target.path, '.')::uuid[]) + JOIN paliad.partner_unit_members pum + ON pum.partner_unit_id = ppu.partner_unit_id + AND pum.user_id = auth.uid() + AND pum.unit_role = ANY(ppu.derive_unit_roles) + WHERE target.id = _project_id + ); +$$; + + +-- +-- Name: FUNCTION can_see_project(_project_id uuid); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.can_see_project(_project_id uuid) IS 'Team-based visibility predicate for paliad.projects. Direct or inherited (ancestor) membership grants access. Global admins see all.'; + + +-- +-- Name: deadline_rule_audit_trigger(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.deadline_rule_audit_trigger() RETURNS trigger + LANGUAGE plpgsql SECURITY DEFINER + SET search_path TO 'paliad', 'public' + AS $$ +DECLARE + v_reason text; + v_action text; + v_before jsonb; + v_after jsonb; + v_rule_id uuid; +BEGIN + v_reason := current_setting('paliad.audit_reason', true); + + IF TG_OP = 'INSERT' THEN + v_action := 'create'; + v_before := NULL; + v_after := to_jsonb(NEW); + v_rule_id := NEW.id; + -- INSERT is allowed without an explicit reason; seed migrations + -- and net-new drafts default to a synthetic reason. + IF v_reason IS NULL OR v_reason = '' THEN + v_reason := 'create'; + END IF; + + ELSIF TG_OP = 'UPDATE' THEN + v_action := 'update'; + v_before := to_jsonb(OLD); + v_after := to_jsonb(NEW); + v_rule_id := NEW.id; + IF v_reason IS NULL OR v_reason = '' THEN + RAISE EXCEPTION 'paliad.deadline_rules: audit reason required for UPDATE — ' + 'set paliad.audit_reason via SET LOCAL or set_config()'; + END IF; + + ELSIF TG_OP = 'DELETE' THEN + v_action := 'delete'; + v_before := to_jsonb(OLD); + v_after := NULL; + v_rule_id := OLD.id; + IF v_reason IS NULL OR v_reason = '' THEN + RAISE EXCEPTION 'paliad.deadline_rules: audit reason required for DELETE — ' + 'set paliad.audit_reason via SET LOCAL or set_config()'; + END IF; + END IF; + + INSERT INTO paliad.deadline_rule_audit + (rule_id, changed_by, action, before_json, after_json, reason) + VALUES + (v_rule_id, auth.uid(), v_action, v_before, v_after, v_reason); + + RETURN COALESCE(NEW, OLD); +END; +$$; + + +-- +-- Name: FUNCTION deadline_rule_audit_trigger(); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.deadline_rule_audit_trigger() IS 'AFTER-trigger backstop that writes paliad.deadline_rule_audit rows for every raw INSERT / UPDATE / DELETE on paliad.deadline_rules. UPDATE / DELETE require paliad.audit_reason to be set in the session (via SET LOCAL paliad.audit_reason = ...); INSERT defaults to ''create'' so seed migrations remain ergonomic.'; + + +-- +-- Name: effective_project_admin(uuid, uuid); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.effective_project_admin(_user_id uuid, _project_id uuid) RETURNS boolean + LANGUAGE sql STABLE SECURITY DEFINER + SET search_path TO 'paliad', 'public' + AS $$ + SELECT EXISTS ( + SELECT 1 FROM paliad.users u + WHERE u.id = _user_id + AND u.global_role = 'global_admin' + ) + OR EXISTS ( + SELECT 1 + FROM paliad.projects target + JOIN paliad.project_teams pt + ON pt.user_id = _user_id + AND pt.responsibility = 'admin' + AND pt.project_id = ANY(string_to_array(target.path, '.')::uuid[]) + WHERE target.id = _project_id + ); +$$; + + +-- +-- Name: FUNCTION effective_project_admin(_user_id uuid, _project_id uuid); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.effective_project_admin(_user_id uuid, _project_id uuid) IS 'True iff the user is global_admin OR has responsibility=admin on the project itself or any ancestor in the materialised ltree path. Drives the role-edit gate on project_teams (UPDATE/INSERT/DELETE RLS).'; + + +-- +-- Name: note_is_visible(uuid, uuid, uuid, uuid); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.note_is_visible(_project_id uuid, _deadline_id uuid, _appointment_id uuid, _project_event_id uuid) RETURNS boolean + LANGUAGE sql STABLE SECURITY DEFINER + SET search_path TO 'paliad', 'public' + AS $$ + SELECT CASE + WHEN _project_id IS NOT NULL THEN paliad.can_see_project(_project_id) + WHEN _deadline_id IS NOT NULL THEN paliad.can_see_project( + (SELECT project_id FROM paliad.deadlines WHERE id = _deadline_id)) + WHEN _appointment_id IS NOT NULL THEN + CASE + WHEN (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id) IS NULL + THEN (SELECT created_by FROM paliad.appointments WHERE id = _appointment_id) = auth.uid() + ELSE paliad.can_see_project( + (SELECT project_id FROM paliad.appointments WHERE id = _appointment_id)) + END + WHEN _project_event_id IS NOT NULL THEN paliad.can_see_project( + (SELECT project_id FROM paliad.project_events WHERE id = _project_event_id)) + ELSE false + END; +$$; + + +-- +-- Name: projects_no_two_level_ccr(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.projects_no_two_level_ccr() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + -- A project that is itself a CCR may NOT be the target of another CCR. + -- Two cases to reject: + -- + -- (a) NEW row points at a parent that is itself a CCR: + -- NEW.counterclaim_of -> some row with counterclaim_of NOT NULL. + -- + -- (b) NEW row claims to be a CCR (NEW.counterclaim_of IS NOT NULL) + -- but already has another CCR pointing AT it (NEW.id is the + -- target of some other row's counterclaim_of). The cleaner + -- phrasing: "no row may simultaneously have a CCR child AND + -- a CCR parent". + IF NEW.counterclaim_of IS NOT NULL THEN + IF EXISTS ( + SELECT 1 FROM paliad.projects p + WHERE p.id = NEW.counterclaim_of + AND p.counterclaim_of IS NOT NULL + ) THEN + RAISE EXCEPTION + 'two-level counterclaim chains are not allowed: parent project % is itself a counterclaim', + NEW.counterclaim_of; + END IF; + + IF EXISTS ( + SELECT 1 FROM paliad.projects p + WHERE p.counterclaim_of = NEW.id + ) THEN + RAISE EXCEPTION + 'project % already has a counterclaim child and cannot itself be a counterclaim', + NEW.id; + END IF; + END IF; + + RETURN NEW; +END; +$$; + + +-- +-- Name: FUNCTION projects_no_two_level_ccr(); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.projects_no_two_level_ccr() IS 'Rejects two-level counterclaim chains. UPC practice does not have CCR-of-a-CCR; reject the malformed shape at write time so the app layer never has to defend against it. See migration 077.'; + + +-- +-- Name: projects_proceeding_type_category_check(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.projects_proceeding_type_category_check() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + v_category text; +BEGIN + IF NEW.proceeding_type_id IS NULL THEN + RETURN NEW; + END IF; + + SELECT category INTO v_category + FROM paliad.proceeding_types + WHERE id = NEW.proceeding_type_id; + + -- The FK on the column guarantees v_category is non-NULL when the + -- id resolves — but defensive against a future FK relax-and-replace. + IF v_category IS NULL THEN + RAISE EXCEPTION + 'paliad.projects.proceeding_type_id = % does not resolve to a ' + 'proceeding_types row — FK constraint should have caught this.', + NEW.proceeding_type_id; + END IF; + + IF v_category <> 'fristenrechner' THEN + RAISE EXCEPTION + 'paliad.projects.proceeding_type_id must reference a ' + 'fristenrechner-category proceeding_types row (got category=''%''). ' + 'Verfahrenstyp muss ein Fristenrechner-Typ sein (Kategorie=''%''). ' + 'Slice 5 (Phase 3 soft-merge per design §3.F) retires the ' + '''litigation'' category for project-binding; pick a UPC_*, ' + 'DE_*, EPA_*, DPMA_* or EP_GRANT code instead.', + v_category, v_category; + END IF; + + RETURN NEW; +END; +$$; + + +-- +-- Name: FUNCTION projects_proceeding_type_category_check(); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.projects_proceeding_type_category_check() IS 'BEFORE INSERT/UPDATE trigger function enforcing the Phase 3 Slice 5 invariant: paliad.projects.proceeding_type_id may only reference fristenrechner-category proceeding_types rows. NULL is allowed.'; + + +-- +-- Name: projects_rewrite_subtree(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.projects_rewrite_subtree() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF OLD.path IS DISTINCT FROM NEW.path THEN + UPDATE paliad.projects + SET path = NEW.path || substring(path FROM length(OLD.path) + 1) + WHERE path LIKE OLD.path || '.%'; + END IF; + RETURN NEW; +END; +$$; + + +-- +-- Name: projects_sync_path(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.projects_sync_path() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + parent_path text; +BEGIN + IF NEW.parent_id IS NULL THEN + NEW.path := NEW.id::text; + ELSE + SELECT path INTO parent_path + FROM paliad.projects + WHERE id = NEW.parent_id; + IF parent_path IS NULL THEN + RAISE EXCEPTION 'parent project % not found', NEW.parent_id; + END IF; + -- Reject cycles: parent's path cannot contain this row's id. + IF parent_path = NEW.id::text + OR parent_path LIKE '%.' || NEW.id::text + OR parent_path LIKE NEW.id::text || '.%' + OR parent_path LIKE '%.' || NEW.id::text || '.%' + THEN + RAISE EXCEPTION 'cannot set parent to own descendant'; + END IF; + NEW.path := parent_path || '.' || NEW.id::text; + END IF; + RETURN NEW; +END; +$$; + + +-- +-- Name: tg_set_updated_at(); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.tg_set_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + + +-- +-- Name: user_project_authority_level(uuid, uuid); Type: FUNCTION; Schema: paliad; Owner: - +-- + +CREATE FUNCTION paliad.user_project_authority_level(_user_id uuid, _project_id uuid) RETURNS integer + LANGUAGE sql STABLE + AS $$ + WITH path AS ( + SELECT string_to_array(p.path, '.')::uuid[] AS ids + FROM paliad.projects p WHERE p.id = _project_id + ), + direct_or_ancestor AS ( + SELECT pt.responsibility + FROM paliad.project_teams pt + JOIN path ON pt.project_id = ANY(path.ids) + WHERE pt.user_id = _user_id + ), + profession_level AS ( + SELECT paliad.approval_role_level(u.profession) AS lvl + FROM paliad.users u WHERE u.id = _user_id + ), + direct_level AS ( + -- Profession-level if any membership row opens the gate, else 0. + SELECT CASE + WHEN EXISTS ( + SELECT 1 FROM direct_or_ancestor doa + WHERE doa.responsibility IN ('lead', 'member') + ) THEN COALESCE((SELECT lvl FROM profession_level), 0) + ELSE 0 + END AS lvl + ), + derived_level AS ( + SELECT COALESCE(MAX(paliad.approval_role_level( + paliad.approval_role_from_unit_role(pum.unit_role) + )), 0) AS lvl + FROM paliad.project_partner_units ppu + JOIN paliad.partner_unit_members pum + ON pum.partner_unit_id = ppu.partner_unit_id + AND pum.user_id = _user_id + AND pum.unit_role = ANY(ppu.derive_unit_roles) + JOIN path ON ppu.project_id = ANY(path.ids) + WHERE ppu.derive_grants_authority = true + ) + SELECT GREATEST( + (SELECT lvl FROM direct_level), + (SELECT lvl FROM derived_level) + ); +$$; + + +-- +-- Name: FUNCTION user_project_authority_level(_user_id uuid, _project_id uuid); Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON FUNCTION paliad.user_project_authority_level(_user_id uuid, _project_id uuid) IS 'Effective approval-ladder level for user U on project P, evaluated as a tuple-with-gate: profession_level if responsibility ∈ {lead,member} else 0; max with derived authority (partner-unit attachment with grants_authority=true). t-paliad-148.'; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: applied_migrations; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.applied_migrations ( + version integer NOT NULL, + name text NOT NULL, + applied_at timestamp with time zone DEFAULT now() NOT NULL, + checksum text +); + + +-- +-- Name: appointment_caldav_targets; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.appointment_caldav_targets ( + appointment_id uuid NOT NULL, + binding_id uuid NOT NULL, + caldav_uid text NOT NULL, + caldav_etag text, + last_pushed_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: appointments; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.appointments ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + title text NOT NULL, + description text, + start_at timestamp with time zone NOT NULL, + end_at timestamp with time zone, + location text, + appointment_type text, + caldav_uid text, + caldav_etag text, + created_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid, + approval_status text DEFAULT 'approved'::text NOT NULL, + pending_request_id uuid, + approved_by uuid, + approved_at timestamp with time zone, + completed_at timestamp with time zone, + deadline_rule_id uuid, + CONSTRAINT appointments_approval_status_check CHECK ((approval_status = ANY (ARRAY['approved'::text, 'pending'::text, 'legacy'::text]))), + CONSTRAINT termine_termin_type_check CHECK (((appointment_type IS NULL) OR (appointment_type = ANY (ARRAY['hearing'::text, 'meeting'::text, 'consultation'::text, 'deadline_hearing'::text])))) +); + + +-- +-- Name: COLUMN appointments.deadline_rule_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.appointments.deadline_rule_id IS 'When non-NULL, this appointment is the actual occurrence of a standard-course rule (Hauptverhandlung, Decision, Order). Anchors downstream re-projection via FristenrechnerService AnchorOverrides. See docs/design-smart-timeline-2026-05-08.md §6.'; + + +-- +-- Name: approval_policies; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.approval_policies ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + project_id uuid, + entity_type text NOT NULL, + lifecycle_event text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + created_by uuid, + partner_unit_id uuid, + requires_approval boolean NOT NULL, + min_role text, + CONSTRAINT approval_policies_entity_type_check CHECK ((entity_type = ANY (ARRAY['deadline'::text, 'appointment'::text]))), + CONSTRAINT approval_policies_lifecycle_event_check CHECK ((lifecycle_event = ANY (ARRAY['create'::text, 'update'::text, 'complete'::text, 'delete'::text]))), + CONSTRAINT approval_policies_min_role_check CHECK (((min_role IS NULL) OR (min_role = ANY (ARRAY['partner'::text, 'of_counsel'::text, 'associate'::text, 'senior_pa'::text, 'pa'::text])))), + CONSTRAINT approval_policies_min_role_xor_required CHECK ((((requires_approval = false) AND (min_role IS NULL)) OR ((requires_approval = true) AND (min_role IS NOT NULL)))), + CONSTRAINT approval_policies_scope_xor CHECK ((((project_id IS NOT NULL) AND (partner_unit_id IS NULL)) OR ((project_id IS NULL) AND (partner_unit_id IS NOT NULL)))) +); + + +-- +-- Name: approval_requests; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.approval_requests ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + project_id uuid NOT NULL, + entity_type text NOT NULL, + entity_id uuid NOT NULL, + lifecycle_event text NOT NULL, + pre_image jsonb, + payload jsonb, + requested_by uuid NOT NULL, + requested_at timestamp with time zone DEFAULT now() NOT NULL, + required_role text NOT NULL, + status text DEFAULT 'pending'::text NOT NULL, + decided_by uuid, + decided_at timestamp with time zone, + decision_kind text, + decision_note text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + requester_kind text DEFAULT 'user'::text NOT NULL, + agent_turn_id uuid, + counter_payload jsonb, + previous_request_id uuid, + CONSTRAINT approval_requests_agent_xor CHECK ((((requester_kind = 'agent'::text) AND (agent_turn_id IS NOT NULL)) OR ((requester_kind = 'user'::text) AND (agent_turn_id IS NULL)))), + CONSTRAINT approval_requests_decision_kind_check CHECK (((decision_kind IS NULL) OR (decision_kind = ANY (ARRAY['peer'::text, 'admin_override'::text, 'derived_peer'::text])))), + CONSTRAINT approval_requests_entity_type_check CHECK ((entity_type = ANY (ARRAY['deadline'::text, 'appointment'::text]))), + CONSTRAINT approval_requests_lifecycle_event_check CHECK ((lifecycle_event = ANY (ARRAY['create'::text, 'update'::text, 'complete'::text, 'delete'::text]))), + CONSTRAINT approval_requests_no_self_approval CHECK (((decided_by IS NULL) OR (decided_by <> requested_by))), + CONSTRAINT approval_requests_requester_kind_check CHECK ((requester_kind = ANY (ARRAY['user'::text, 'agent'::text]))), + CONSTRAINT approval_requests_required_role_check CHECK ((required_role = ANY (ARRAY['partner'::text, 'of_counsel'::text, 'associate'::text, 'senior_pa'::text, 'pa'::text]))), + CONSTRAINT approval_requests_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'approved'::text, 'rejected'::text, 'revoked'::text, 'superseded'::text, 'changes_requested'::text]))) +); + + +-- +-- Name: COLUMN approval_requests.requester_kind; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.approval_requests.requester_kind IS 'Who originated the request: ''user'' (direct user create) or ''agent'' (Paliadin drafted it from a chat turn, awaiting user approval). Default ''user'' so existing audit rows backfill cleanly.'; + + +-- +-- Name: COLUMN approval_requests.agent_turn_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.approval_requests.agent_turn_id IS 'When requester_kind=''agent'', the paliadin_turns row the suggestion came from. NULL otherwise. ON DELETE SET NULL so the audit record survives if the turn row is later purged.'; + + +-- +-- Name: backups; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.backups ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + kind text NOT NULL, + status text NOT NULL, + requested_by uuid, + requested_by_email text NOT NULL, + audit_id uuid, + storage_uri text, + size_bytes bigint, + row_counts jsonb DEFAULT '{}'::jsonb NOT NULL, + sheet_count integer, + warnings jsonb DEFAULT '[]'::jsonb NOT NULL, + error text, + started_at timestamp with time zone DEFAULT now() NOT NULL, + finished_at timestamp with time zone, + deleted_at timestamp with time zone, + CONSTRAINT backups_kind_check CHECK ((kind = ANY (ARRAY['scheduled'::text, 'on_demand'::text]))), + CONSTRAINT backups_status_check CHECK ((status = ANY (ARRAY['running'::text, 'done'::text, 'failed'::text]))) +); + + +-- +-- Name: TABLE backups; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.backups IS 'Catalog of org-scope backup runs (t-paliad-246 / m/paliad#77). One row per scheduled or on-demand backup. status transitions: running → done | failed. storage_uri is resolved by the Go-side ArtifactStore interface. audit_id links to system_audit_log; the catalog row is the richer-shape duplicate, the audit row is the trust signal.'; + + +-- +-- Name: COLUMN backups.requested_by_email; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.backups.requested_by_email IS 'Captured at write time so the row survives user deletion. Sentinel ''system@paliad'' for scheduled runs.'; + + +-- +-- Name: COLUMN backups.storage_uri; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.backups.storage_uri IS 'Resolved by the Go-side ArtifactStore implementation. file://... for LocalDiskStore; future stores use their own URI scheme.'; + + +-- +-- Name: COLUMN backups.deleted_at; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.backups.deleted_at IS 'Set when the artifact is removed from storage by lifecycle cleanup. Catalog row stays forever (audit chain). NULL means artifact is still on disk.'; + + +-- +-- Name: caldav_sync_log; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.caldav_sync_log ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + occurred_at timestamp with time zone DEFAULT now() NOT NULL, + direction text NOT NULL, + items_pushed integer DEFAULT 0 NOT NULL, + items_pulled integer DEFAULT 0 NOT NULL, + error text, + duration_ms integer, + binding_id uuid, + CONSTRAINT caldav_sync_log_direction_check CHECK ((direction = ANY (ARRAY['push'::text, 'pull'::text, 'both'::text]))) +); + + +-- +-- Name: checklist_feedback; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.checklist_feedback ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + feedback_type text NOT NULL, + checklist text DEFAULT ''::text NOT NULL, + message text NOT NULL, + submitted_by text DEFAULT ''::text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: checklist_instances; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.checklist_instances ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + template_slug text NOT NULL, + name text NOT NULL, + state jsonb DEFAULT '{}'::jsonb NOT NULL, + created_by uuid NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid, + template_snapshot jsonb, + template_version integer +); + + +-- +-- Name: COLUMN checklist_instances.template_snapshot; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.checklist_instances.template_snapshot IS 'Snapshot of the template body at instance create time. NULL for pre-mig-114 rows; service layer falls back to live catalog lookup in that case (legacy path; backfilled in Slice C).'; + + +-- +-- Name: COLUMN checklist_instances.template_version; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.checklist_instances.template_version IS 'Snapshot of paliad.checklists.version at instance create time. NULL for pre-Slice-C rows where the version wasn''t captured; the "outdated" badge stays off in that case.'; + + +-- +-- Name: checklist_shares; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.checklist_shares ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + checklist_id uuid NOT NULL, + recipient_kind text NOT NULL, + recipient_user_id uuid, + recipient_office text, + recipient_partner_unit_id uuid, + recipient_project_id uuid, + granted_by uuid NOT NULL, + granted_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT checklist_shares_recipient_kind_check CHECK ((recipient_kind = ANY (ARRAY['user'::text, 'office'::text, 'partner_unit'::text, 'project'::text]))), + CONSTRAINT checklist_shares_recipient_xor CHECK ((((recipient_kind = 'user'::text) AND (recipient_user_id IS NOT NULL) AND (recipient_office IS NULL) AND (recipient_partner_unit_id IS NULL) AND (recipient_project_id IS NULL)) OR ((recipient_kind = 'office'::text) AND (recipient_office IS NOT NULL) AND (recipient_user_id IS NULL) AND (recipient_partner_unit_id IS NULL) AND (recipient_project_id IS NULL)) OR ((recipient_kind = 'partner_unit'::text) AND (recipient_partner_unit_id IS NOT NULL) AND (recipient_user_id IS NULL) AND (recipient_office IS NULL) AND (recipient_project_id IS NULL)) OR ((recipient_kind = 'project'::text) AND (recipient_project_id IS NOT NULL) AND (recipient_user_id IS NULL) AND (recipient_office IS NULL) AND (recipient_partner_unit_id IS NULL)))) +); + + +-- +-- Name: TABLE checklist_shares; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.checklist_shares IS 'Explicit grants for paliad.checklists. Polymorphic recipient (user/office/partner_unit/project) enforced by recipient_xor CHECK. Owner of the checklist grants and revokes; global_admin can revoke as well. Slice B (t-paliad-225) — see can_see_checklist body for the visibility branches that consume these rows.'; + + +-- +-- Name: checklists; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.checklists ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + slug text NOT NULL, + owner_id uuid NOT NULL, + title text NOT NULL, + description text DEFAULT ''::text NOT NULL, + regime text DEFAULT 'OTHER'::text NOT NULL, + court text DEFAULT ''::text NOT NULL, + reference text DEFAULT ''::text NOT NULL, + deadline text DEFAULT ''::text NOT NULL, + lang text DEFAULT 'de'::text NOT NULL, + body jsonb NOT NULL, + visibility text DEFAULT 'private'::text NOT NULL, + promoted_at timestamp with time zone, + promoted_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + version integer DEFAULT 1 NOT NULL, + CONSTRAINT checklists_visibility_check CHECK ((visibility = ANY (ARRAY['private'::text, 'shared'::text, 'firm'::text, 'global'::text]))) +); + + +-- +-- Name: TABLE checklists; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.checklists IS 'User-authored checklist templates. Augments the static Go catalog at read time via ChecklistCatalogService. Visibility levels: private (owner only), shared (Slice B), firm (all authenticated), global (admin-promoted into firm catalog — Slice B).'; + + +-- +-- Name: COLUMN checklists.version; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.checklists.version IS 'Monotonic version counter, bumps in ChecklistTemplateService.Update whenever body or title changes. Used by the instance detail page to show an "outdated" badge when the user''s snapshot is older.'; + + +-- +-- Name: countries; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.countries ( + code text NOT NULL, + name_de text NOT NULL, + name_en text NOT NULL, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: court_feedback; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.court_feedback ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + court_id text DEFAULT ''::text NOT NULL, + feedback_type text NOT NULL, + message text NOT NULL, + submitted_by text DEFAULT ''::text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: courts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.courts ( + id text NOT NULL, + code text NOT NULL, + name_de text NOT NULL, + name_en text NOT NULL, + country text NOT NULL, + regime text, + court_type text NOT NULL, + parent_id text, + sort_order integer DEFAULT 0 NOT NULL, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT courts_regime_chk CHECK (((regime IS NULL) OR (regime = ANY (ARRAY['UPC'::text, 'EPO'::text])))) +); + + +-- +-- Name: deadline_concept_event_types; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_concept_event_types ( + concept_id uuid NOT NULL, + event_type_id uuid NOT NULL, + is_default boolean DEFAULT false NOT NULL, + sort_order integer DEFAULT 100 NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + jurisdiction text +); + + +-- +-- Name: deadline_concepts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_concepts ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + slug text NOT NULL, + name_de text NOT NULL, + name_en text NOT NULL, + description text, + aliases text[] DEFAULT '{}'::text[] NOT NULL, + party text, + category text DEFAULT 'submission'::text NOT NULL, + sort_order integer DEFAULT 100 NOT NULL, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT deadline_concepts_category_check CHECK ((category = ANY (ARRAY['submission'::text, 'decision'::text, 'order'::text, 'hearing'::text, 'other'::text]))) +); + + +-- +-- Name: TABLE deadline_concepts; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_concepts IS 'The Unifier layer above paliad.deadline_rules. One row per legal concept (Klageerwiderung, Berufungsfrist, …); deadline_rules.concept_id links rule rows from different proceeding-types to the same concept.'; + + +-- +-- Name: COLUMN deadline_concepts.aliases; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_concepts.aliases IS 'Synonyms (DE, EN, colloquial) the search bar matches against. Curated in seed migrations; not user-editable in v1.'; + + +-- +-- Name: COLUMN deadline_concepts.party; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_concepts.party IS 'Dominant party across contexts (claimant / defendant / both / court). Per-rule paliad.deadline_rules.primary_party overrides for cases where a context has a different responsible party.'; + + +-- +-- Name: deadline_event_types; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_event_types ( + deadline_id uuid NOT NULL, + event_type_id uuid NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: deadline_rule_audit; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rule_audit ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + rule_id uuid NOT NULL, + changed_by uuid, + changed_at timestamp with time zone DEFAULT now() NOT NULL, + action text NOT NULL, + before_json jsonb, + after_json jsonb, + reason text NOT NULL, + migration_exported boolean DEFAULT false NOT NULL, + CONSTRAINT deadline_rule_audit_action_check CHECK ((action = ANY (ARRAY['create'::text, 'update'::text, 'delete'::text, 'publish'::text, 'archive'::text, 'restore'::text]))) +); + + +-- +-- Name: TABLE deadline_rule_audit; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_rule_audit IS 'Append-only audit log for paliad.deadline_rules. Written by the AFTER-trigger on the rules table (raw create/update/delete) and by the Go rule-editor service (semantic publish/archive/restore). Required reason field is the compliance hook for the rule-editor design (Q5, §4.7).'; + + +-- +-- Name: deadline_rule_backfill_orphans; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rule_backfill_orphans ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + deadline_id uuid NOT NULL, + title text NOT NULL, + project_id uuid, + proceeding_code text, + reason text NOT NULL, + candidate_count integer DEFAULT 0 NOT NULL, + candidate_rule_ids uuid[] DEFAULT '{}'::uuid[] NOT NULL, + resolved_at timestamp with time zone, + resolved_rule_id uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT deadline_rule_backfill_orphans_reason_check CHECK ((reason = ANY (ARRAY['no_match'::text, 'ambiguous'::text, 'no_project'::text, 'manual_unbound'::text]))) +); + + +-- +-- Name: TABLE deadline_rule_backfill_orphans; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_rule_backfill_orphans IS 'Slice 10 (mig 089/090, t-paliad-190): staging for legacy paliad.deadlines rows that the fuzzy-match backfill could not uniquely bind to a deadline_rule. Each row holds the deadline context + the candidate rule IDs the matcher found (0 → ''no_match''; ≥2 → ''ambiguous'') so a legal-review pass can hand-link without rerunning the match. resolved_at + resolved_rule_id flip when the admin orphan-review UI binds the row.'; + + +-- +-- Name: deadline_rules; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rules ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + proceeding_type_id integer, + parent_id uuid, + submission_code text, + name text NOT NULL, + description text, + primary_party text, + event_type text, + duration_value integer DEFAULT 0 NOT NULL, + duration_unit text DEFAULT 'months'::text NOT NULL, + timing text DEFAULT 'after'::text, + rule_code text, + deadline_notes text, + sequence_order integer DEFAULT 0 NOT NULL, + alt_duration_value integer, + alt_duration_unit text, + alt_rule_code text, + is_spawn boolean DEFAULT false NOT NULL, + spawn_label text, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + name_en text DEFAULT ''::text NOT NULL, + anchor_alt text, + deadline_notes_en text, + concept_id uuid, + legal_source text, + is_bilateral boolean DEFAULT false NOT NULL, + trigger_event_id bigint, + spawn_proceeding_type_id integer, + combine_op text, + condition_expr jsonb, + priority text DEFAULT 'mandatory'::text NOT NULL, + is_court_set boolean DEFAULT false NOT NULL, + lifecycle_state text DEFAULT 'published'::text NOT NULL, + draft_of uuid, + published_at timestamp with time zone, + rule_codes text[], + choices_offered jsonb, + CONSTRAINT deadline_rules_alt_duration_unit_check CHECK (((alt_duration_unit IS NULL) OR (alt_duration_unit = ANY (ARRAY['days'::text, 'weeks'::text, 'months'::text, 'working_days'::text])))), + CONSTRAINT deadline_rules_combine_op_check CHECK (((combine_op IS NULL) OR (combine_op = ANY (ARRAY['max'::text, 'min'::text])))), + CONSTRAINT deadline_rules_duration_unit_check CHECK ((duration_unit = ANY (ARRAY['days'::text, 'weeks'::text, 'months'::text, 'working_days'::text]))), + CONSTRAINT deadline_rules_lifecycle_state_check CHECK ((lifecycle_state = ANY (ARRAY['draft'::text, 'published'::text, 'archived'::text]))), + CONSTRAINT deadline_rules_priority_check CHECK ((priority = ANY (ARRAY['mandatory'::text, 'recommended'::text, 'optional'::text, 'informational'::text]))) +); + + +-- +-- Name: COLUMN deadline_rules.anchor_alt; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.anchor_alt IS 'Named alternate anchor. ''priority_date'' means: when the API request supplies priorityDate, use it as the base for this rule''s duration instead of the trigger date or the parent rule''s computed date.'; + + +-- +-- Name: COLUMN deadline_rules.deadline_notes_en; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.deadline_notes_en IS 'English translation of deadline_notes. Render path prefers this column when the active locale is EN; falls back to deadline_notes (DE) when NULL.'; + + +-- +-- Name: COLUMN deadline_rules.concept_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.concept_id IS 'Links this rule to its canonical paliad.deadline_concepts entry. NULL until backfilled in migration 040.'; + + +-- +-- Name: COLUMN deadline_rules.legal_source; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.legal_source IS 'Structured citation code per t-paliad-131 §4.5: ..<§/Art./R>[.][.]. Distinct from rule_code.'; + + +-- +-- Name: COLUMN deadline_rules.trigger_event_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.trigger_event_id IS 'Optional FK to paliad.trigger_events. When non-NULL, this rule is event-rooted (Pipeline C unification, design §2.5). When NULL the rule is proceeding-rooted via proceeding_type_id. Exactly one of the two must be set after Slice 3 backfill (enforced by a CHECK constraint added in Slice 9 after legacy callers retire).'; + + +-- +-- Name: COLUMN deadline_rules.spawn_proceeding_type_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.spawn_proceeding_type_id IS 'When is_spawn=true, points at the target proceeding whose rule set the calculator follows when this rule fires (cross-proceeding spawn, design §2.6). Backfilled in Slice 7 for the 8 live spawn rules.'; + + +-- +-- Name: COLUMN deadline_rules.combine_op; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.combine_op IS 'NULL = single-anchor arithmetic. ''max'' / ''min'' = composite-rule arithmetic combining (duration_value, duration_unit) with (alt_duration_value, alt_duration_unit). Used by R.198 / R.213 ("31d OR 20 working_days, whichever is longer / shorter").'; + + +-- +-- Name: COLUMN deadline_rules.condition_expr; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.condition_expr IS 'jsonb gating expression replacing condition_flag (Q6, design §2.4). Grammar: {"flag": ""} | {"op":"and"|"or", "args":[...]} | {"op":"not", "args":[]}. NULL or {} = unconditional. Backfilled in Slice 2 from condition_flag; new code reads this, falls back to condition_flag during the transition window.'; + + +-- +-- Name: COLUMN deadline_rules.priority; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.priority IS 'Unified 4-way enum (Q3, design §2.3) replacing the is_mandatory + is_optional pair. Allowed: mandatory | recommended | optional | informational. Default ''mandatory'' on new rows; legacy rows get backfilled in Slice 2 from the (is_mandatory, is_optional) pair.'; + + +-- +-- Name: COLUMN deadline_rules.is_court_set; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.is_court_set IS 'Replaces the runtime heuristic (primary_party=''court'' OR event_type IN (...)) with an explicit column (Q12). Default false on new rows; Slice 2 backfills from the heuristic so behaviour is unchanged at first.'; + + +-- +-- Name: COLUMN deadline_rules.lifecycle_state; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.lifecycle_state IS 'Rule-editor lifecycle (Q5, design §4.2). draft = work-in-progress admin edit; published = live, calculator-visible; archived = historical (kept for audit). Default ''published'' so every existing row stays live without an UPDATE.'; + + +-- +-- Name: COLUMN deadline_rules.draft_of; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.draft_of IS 'When lifecycle_state=''draft'', points at the published rule this draft will replace on publish. NULL on published or archived rows. NULL also on net-new drafts (no prior published peer).'; + + +-- +-- Name: COLUMN deadline_rules.published_at; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.published_at IS 'Timestamp this row entered lifecycle_state=''published''. NULL while draft, populated on publish, retained through archive. Distinct from updated_at (which moves on every edit).'; + + +-- +-- Name: COLUMN deadline_rules.rule_codes; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.rule_codes IS 'Array of legal-rule citations attached to this deadline, in render order. Pipeline-C rules (event-rooted, trigger_event_id IS NOT NULL) populate this column from the legacy paliad.event_deadline_rule_codes junction (mig 092 backfill); Pipeline-A rules use the singular rule_code column instead. NULL on Pipeline-A rules + on the 7 Pipeline-C deadlines that had no junction rows pre-mig.'; + + +-- +-- Name: COLUMN deadline_rules.choices_offered; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadline_rules.choices_offered IS 'Declares which per-card choice-kinds this rule offers on the Verfahrensablauf timeline. NULL = no caret affordance (default). Example shapes: {"appellant": ["claimant","defendant","both","none"]} on decision rules, {"skip": [true, false]} on optional rules, {"include_ccr": [true, false]} on Klageerwiderung rules. Engine and frontend read it; storing per-kind value lists keeps the contract self-describing.'; + + +-- +-- Name: deadline_rules_pre_091; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rules_pre_091 ( + id uuid, + is_mandatory boolean, + is_optional boolean, + condition_flag text[], + condition_rule_id uuid, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE deadline_rules_pre_091; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_rules_pre_091 IS 'Snapshot of paliad.deadline_rules.(is_mandatory, is_optional, condition_flag, condition_rule_id) before mig 091''s drop. Lets a rollback restore the legacy values for the 172 rules that existed at drop time. Drop this table after Slice 9 is verified in prod (a focused follow-up slice or part of Slice 12 cleanup).'; + + +-- +-- Name: deadline_rules_pre_093; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rules_pre_093 ( + id uuid, + proceeding_type_id integer, + parent_id uuid, + code text, + name text, + description text, + primary_party text, + event_type text, + duration_value integer, + duration_unit text, + timing text, + rule_code text, + deadline_notes text, + sequence_order integer, + alt_duration_value integer, + alt_duration_unit text, + alt_rule_code text, + is_spawn boolean, + spawn_label text, + is_active boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name_en text, + anchor_alt text, + deadline_notes_en text, + concept_id uuid, + legal_source text, + is_bilateral boolean, + trigger_event_id bigint, + spawn_proceeding_type_id integer, + combine_op text, + condition_expr jsonb, + priority text, + is_court_set boolean, + lifecycle_state text, + draft_of uuid, + published_at timestamp with time zone, + rule_codes text[], + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE deadline_rules_pre_093; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_rules_pre_093 IS 'Snapshot of the 40 paliad.deadline_rules rows that pointed at litigation-category proceeding_types before mig 093 re-homed them under the _archived_litigation pt. Source-of-truth for the down migration; persists post-drop as the permanent audit record of the Pipeline-A rule corpus.'; + + +-- +-- Name: deadline_rules_pre_095; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rules_pre_095 ( + id uuid, + proceeding_type_id integer, + parent_id uuid, + code text, + name text, + description text, + primary_party text, + event_type text, + duration_value integer, + duration_unit text, + timing text, + rule_code text, + deadline_notes text, + sequence_order integer, + alt_duration_value integer, + alt_duration_unit text, + alt_rule_code text, + is_spawn boolean, + spawn_label text, + is_active boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name_en text, + anchor_alt text, + deadline_notes_en text, + concept_id uuid, + legal_source text, + is_bilateral boolean, + trigger_event_id bigint, + spawn_proceeding_type_id integer, + combine_op text, + condition_expr jsonb, + priority text, + is_court_set boolean, + lifecycle_state text, + draft_of uuid, + published_at timestamp with time zone, + rule_codes text[], + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE deadline_rules_pre_095; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadline_rules_pre_095 IS 'Snapshot of the 4 de_inf.* deadline_rules rows that mig 095 PATCHed (t-paliad-205). Source-of-truth for the down migration; persists post-patch as the permanent audit record. Drop with a focused follow-up after the gap-fill is verified in prod.'; + + +-- +-- Name: deadline_rules_pre_098; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadline_rules_pre_098 ( + id uuid, + proceeding_type_id integer, + parent_id uuid, + code text, + name text, + description text, + primary_party text, + event_type text, + duration_value integer, + duration_unit text, + timing text, + rule_code text, + deadline_notes text, + sequence_order integer, + alt_duration_value integer, + alt_duration_unit text, + alt_rule_code text, + is_spawn boolean, + spawn_label text, + is_active boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + name_en text, + anchor_alt text, + deadline_notes_en text, + concept_id uuid, + legal_source text, + is_bilateral boolean, + trigger_event_id bigint, + spawn_proceeding_type_id integer, + combine_op text, + condition_expr jsonb, + priority text, + is_court_set boolean, + lifecycle_state text, + draft_of uuid, + published_at timestamp with time zone, + rule_codes text[], + snapshotted_at timestamp with time zone +); + + +-- +-- Name: proceeding_types; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.proceeding_types ( + id integer NOT NULL, + code text NOT NULL, + name text NOT NULL, + description text, + jurisdiction text, + category text, + default_color text DEFAULT '#3b82f6'::text NOT NULL, + sort_order integer DEFAULT 0 NOT NULL, + is_active boolean DEFAULT true NOT NULL, + name_en text DEFAULT ''::text NOT NULL, + display_order integer DEFAULT 999 NOT NULL, + trigger_event_label_de text, + trigger_event_label_en text, + CONSTRAINT paliad_proceeding_code_shape CHECK (((code ~ '^[a-z]+\.[a-z]+\.[a-z]+$'::text) OR (code ~ '^_archived_'::text))) +); + + +-- +-- Name: COLUMN proceeding_types.display_order; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.proceeding_types.display_order IS 'Pill-ordering rank by real-world frequency (lower = shown first). Independent of sort_order, which groups by jurisdiction for the Verfahrensablauf wizard. Default 999 = appears at the end.'; + + +-- +-- Name: trigger_events; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.trigger_events ( + id bigint NOT NULL, + code text NOT NULL, + name text NOT NULL, + name_de text DEFAULT ''::text NOT NULL, + description text DEFAULT ''::text NOT NULL, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + concept_id text +); + + +-- +-- Name: COLUMN trigger_events.concept_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.trigger_events.concept_id IS 'Slug of the paliad.deadline_concepts row this trigger represents. Soft link.'; + + +-- +-- Name: deadline_search; Type: MATERIALIZED VIEW; Schema: paliad; Owner: - +-- + +CREATE MATERIALIZED VIEW paliad.deadline_search AS + SELECT 'rule'::text AS kind, + ('r:'::text || (dr.id)::text) AS row_key, + dc.id AS concept_id, + dc.slug AS concept_slug, + dc.name_de AS concept_name_de, + dc.name_en AS concept_name_en, + dc.description AS concept_description, + dc.aliases AS concept_aliases, + dc.party AS concept_party, + dc.category AS concept_category, + dc.sort_order AS concept_sort_order, + dr.id AS rule_id, + NULL::bigint AS trigger_event_id, + pt.code AS proceeding_code, + pt.name AS proceeding_name_de, + pt.name_en AS proceeding_name_en, + pt.jurisdiction, + pt.display_order AS proceeding_display_order, + dr.submission_code AS rule_local_code, + dr.name AS rule_name_de, + dr.name_en AS rule_name_en, + dr.legal_source, + dr.rule_code, + dr.duration_value, + dr.duration_unit, + dr.timing, + COALESCE(dr.primary_party, dc.party) AS effective_party + FROM ((paliad.deadline_rules dr + JOIN paliad.proceeding_types pt ON ((pt.id = dr.proceeding_type_id))) + JOIN paliad.deadline_concepts dc ON ((dc.id = dr.concept_id))) + WHERE (dr.is_active AND pt.is_active AND (pt.category = 'fristenrechner'::text)) +UNION ALL + SELECT 'trigger'::text AS kind, + ('t:'::text || (te.id)::text) AS row_key, + dc.id AS concept_id, + dc.slug AS concept_slug, + dc.name_de AS concept_name_de, + dc.name_en AS concept_name_en, + dc.description AS concept_description, + dc.aliases AS concept_aliases, + dc.party AS concept_party, + dc.category AS concept_category, + dc.sort_order AS concept_sort_order, + NULL::uuid AS rule_id, + te.id AS trigger_event_id, + NULL::text AS proceeding_code, + NULL::text AS proceeding_name_de, + NULL::text AS proceeding_name_en, + 'cross-cutting'::text AS jurisdiction, + 9999 AS proceeding_display_order, + te.code AS rule_local_code, + te.name_de AS rule_name_de, + te.name AS rule_name_en, + dr_trig.legal_source, + NULL::text AS rule_code, + NULL::integer AS duration_value, + NULL::text AS duration_unit, + NULL::text AS timing, + dc.party AS effective_party + FROM ((paliad.trigger_events te + JOIN paliad.deadline_concepts dc ON ((dc.slug = te.concept_id))) + LEFT JOIN paliad.deadline_rules dr_trig ON (((dr_trig.trigger_event_id = te.id) AND (dr_trig.proceeding_type_id IS NULL) AND dr_trig.is_active AND (dr_trig.lifecycle_state = 'published'::text)))) + WHERE te.is_active + WITH NO DATA; + + +-- +-- Name: deadlines; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadlines ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + title text NOT NULL, + description text, + due_date date NOT NULL, + original_due_date date, + warning_date date, + source text DEFAULT 'manual'::text NOT NULL, + rule_id uuid, + status text DEFAULT 'pending'::text NOT NULL, + completed_at timestamp with time zone, + caldav_uid text, + caldav_etag text, + notes text, + created_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid NOT NULL, + rule_code text, + approval_status text DEFAULT 'approved'::text NOT NULL, + pending_request_id uuid, + approved_by uuid, + approved_at timestamp with time zone, + custom_rule_text text, + CONSTRAINT deadlines_approval_status_check CHECK ((approval_status = ANY (ARRAY['approved'::text, 'pending'::text, 'legacy'::text]))), + CONSTRAINT deadlines_source_check CHECK ((source = ANY (ARRAY['manual'::text, 'fristenrechner'::text, 'rule'::text, 'import'::text, 'anchor'::text]))), + CONSTRAINT fristen_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'completed'::text, 'cancelled'::text, 'waived'::text]))) +); + + +-- +-- Name: COLUMN deadlines.rule_code; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadlines.rule_code IS 'Legal rule citation (e.g. "RoP.023") as it should appear in REGEL. Free text — not FK-constrained — so the value survives rule-table rewrites and accepts citations from sources outside paliad.deadline_rules.'; + + +-- +-- Name: COLUMN deadlines.custom_rule_text; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.deadlines.custom_rule_text IS 'Free-text rule label entered when the lawyer chose Custom on the deadline form (t-paliad-258). Mutually exclusive with rule_id at the application layer: Auto path sets rule_id and leaves this NULL; Custom path sets this and leaves rule_id NULL. Display surfaces prefer the rule_id-joined deadline_rules.name when present, else fall back to custom_rule_text + a "Custom" badge.'; + + +-- +-- Name: deadlines_pre_089; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.deadlines_pre_089 ( + id uuid, + project_id uuid, + title text, + rule_id uuid, + rule_code text, + status text, + due_date date, + completed_at timestamp with time zone, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +-- +-- Name: TABLE deadlines_pre_089; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.deadlines_pre_089 IS 'Snapshot of paliad.deadlines (id, rule_id-relevant columns) taken before mig 090 ran the fuzzy-match backfill. Lets an operator restore individual rule_id values if a hand-link goes wrong. Slice 11 (rule editor) drops this once orphan resolution finishes.'; + + +-- +-- Name: documents; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.documents ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + title text NOT NULL, + doc_type text, + file_path text, + file_size bigint, + mime_type text, + ai_extracted jsonb, + uploaded_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid NOT NULL +); + + +-- +-- Name: email_broadcasts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.email_broadcasts ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + subject text NOT NULL, + body text NOT NULL, + sender_id uuid NOT NULL, + template_key text, + recipient_filter jsonb DEFAULT '{}'::jsonb NOT NULL, + recipient_user_ids uuid[] DEFAULT '{}'::uuid[] NOT NULL, + send_report jsonb DEFAULT '{}'::jsonb NOT NULL, + sent_at timestamp with time zone DEFAULT now() NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: email_template_versions; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.email_template_versions ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + key text NOT NULL, + lang text NOT NULL, + subject text DEFAULT ''::text NOT NULL, + body text NOT NULL, + saved_at timestamp with time zone DEFAULT now() NOT NULL, + saved_by uuid, + note text DEFAULT ''::text NOT NULL, + CONSTRAINT email_template_versions_lang_check CHECK ((lang = ANY (ARRAY['de'::text, 'en'::text]))) +); + + +-- +-- Name: email_templates; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.email_templates ( + key text NOT NULL, + lang text NOT NULL, + subject text DEFAULT ''::text NOT NULL, + body text NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + updated_by uuid, + CONSTRAINT email_templates_lang_check CHECK ((lang = ANY (ARRAY['de'::text, 'en'::text]))) +); + + +-- +-- Name: event_categories; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.event_categories ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + parent_id uuid, + slug text NOT NULL, + label_de text NOT NULL, + label_en text NOT NULL, + description_de text, + description_en text, + step_question_de text, + step_question_en text, + icon text, + sort_order integer DEFAULT 100 NOT NULL, + is_leaf boolean DEFAULT false NOT NULL, + is_active boolean DEFAULT true NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + forums text[], + party text[], + CONSTRAINT event_categories_forums_check CHECK (((forums IS NULL) OR (forums <@ ARRAY['upc'::text, 'de'::text, 'epa'::text, 'dpma'::text]))), + CONSTRAINT event_categories_party_check CHECK (((party IS NULL) OR (party <@ ARRAY['claimant'::text, 'defendant'::text, 'both'::text, 'court'::text]))) +); + + +-- +-- Name: COLUMN event_categories.forums; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.event_categories.forums IS 'Coarse forum tags driving the #15 inbox-channel chip narrowing. Allowed: upc, de, epa, dpma. NULL = neutral (visible from every inbox setting). Empty array is treated the same as NULL by the frontend filter.'; + + +-- +-- Name: COLUMN event_categories.party; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.event_categories.party IS 'Coarse party tags for the #15 perspective chip on the B1 panel (Slice 3c). Allowed: claimant, defendant, both, court. NULL = neutral.'; + + +-- +-- Name: event_category_concepts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.event_category_concepts ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + event_category_id uuid NOT NULL, + concept_id uuid NOT NULL, + proceeding_type_code text, + sort_order integer DEFAULT 100 NOT NULL +); + + +-- +-- Name: event_deadline_rule_codes_pre_092; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.event_deadline_rule_codes_pre_092 ( + event_deadline_id bigint, + rule_code text, + sort_order integer, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE event_deadline_rule_codes_pre_092; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.event_deadline_rule_codes_pre_092 IS 'Snapshot of paliad.event_deadline_rule_codes before mig 092 dropped it. Restored by the down migration; persists post-drop as the permanent audit record of the legacy RoP citations attached to Pipeline-C deadlines (72 rows across 70 of 77 deadlines).'; + + +-- +-- Name: event_deadlines_pre_092; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.event_deadlines_pre_092 ( + id bigint, + trigger_event_id bigint, + title text, + title_de text, + duration_value integer, + duration_unit text, + timing text, + notes text, + alt_duration_value integer, + alt_duration_unit text, + combine_op text, + is_active boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + notes_en text, + legal_source text, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE event_deadlines_pre_092; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.event_deadlines_pre_092 IS 'Snapshot of paliad.event_deadlines before mig 092 dropped it. Source-of-truth for the down migration; persists post-drop as the permanent audit record of the 77 Pipeline-C source rows that seeded paliad.deadline_rules via mig 085. Drop with a focused follow-up after Slice 9 is verified in prod (pair with paliad.deadline_rules_pre_091 cleanup).'; + + +-- +-- Name: event_types; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.event_types ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + slug text NOT NULL, + label_de text NOT NULL, + label_en text NOT NULL, + category text DEFAULT 'submission'::text NOT NULL, + jurisdiction text, + description text DEFAULT ''::text NOT NULL, + trigger_event_id bigint, + created_by uuid, + is_firm_wide boolean DEFAULT false NOT NULL, + archived_at timestamp with time zone, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT event_types_category_check CHECK ((category = ANY (ARRAY['submission'::text, 'decision'::text, 'order'::text, 'service'::text, 'fee'::text, 'hearing'::text, 'other'::text]))), + CONSTRAINT event_types_jurisdiction_check CHECK (((jurisdiction IS NULL) OR (jurisdiction = ANY (ARRAY['UPC'::text, 'EPO'::text, 'DPMA'::text, 'DE'::text, 'any'::text])))) +); + + +-- +-- Name: firm_dashboard_default; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.firm_dashboard_default ( + id smallint DEFAULT 1 NOT NULL, + layout_json jsonb NOT NULL, + updated_by uuid, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT firm_dashboard_default_id_check CHECK ((id = 1)) +); + + +-- +-- Name: holidays; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.holidays ( + id integer NOT NULL, + date date NOT NULL, + name text NOT NULL, + country text, + state text, + holiday_type text DEFAULT 'public_holiday'::text NOT NULL, + regime text, + CONSTRAINT holidays_country_or_regime_chk CHECK (((country IS NOT NULL) OR (regime IS NOT NULL))), + CONSTRAINT holidays_holiday_type_check CHECK ((holiday_type = ANY (ARRAY['public_holiday'::text, 'closure'::text, 'vacation'::text]))), + CONSTRAINT holidays_regime_chk CHECK (((regime IS NULL) OR (regime = ANY (ARRAY['UPC'::text, 'EPO'::text])))) +); + + +-- +-- Name: holidays_id_seq; Type: SEQUENCE; Schema: paliad; Owner: - +-- + +CREATE SEQUENCE paliad.holidays_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: holidays_id_seq; Type: SEQUENCE OWNED BY; Schema: paliad; Owner: - +-- + +ALTER SEQUENCE paliad.holidays_id_seq OWNED BY paliad.holidays.id; + + +-- +-- Name: invitations; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.invitations ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + from_user_id uuid NOT NULL, + to_email text NOT NULL, + message text DEFAULT ''::text NOT NULL, + sent_at timestamp with time zone DEFAULT now() NOT NULL, + accepted_at timestamp with time zone +); + + +-- +-- Name: link_feedback; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.link_feedback ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + link_id text NOT NULL, + feedback_type text NOT NULL, + message text DEFAULT ''::text NOT NULL, + submitted_by text DEFAULT ''::text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: link_suggestions; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.link_suggestions ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + title text NOT NULL, + url text NOT NULL, + category text NOT NULL, + description text DEFAULT ''::text NOT NULL, + suggested_by text DEFAULT ''::text NOT NULL, + status text DEFAULT 'pending'::text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT link_suggestions_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'approved'::text, 'rejected'::text]))) +); + + +-- +-- Name: notes; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.notes ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + deadline_id uuid, + appointment_id uuid, + project_event_id uuid, + content text NOT NULL, + created_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid, + CONSTRAINT notizen_exactly_one_parent CHECK ((((( +CASE + WHEN (project_id IS NOT NULL) THEN 1 + ELSE 0 +END + +CASE + WHEN (deadline_id IS NOT NULL) THEN 1 + ELSE 0 +END) + +CASE + WHEN (appointment_id IS NOT NULL) THEN 1 + ELSE 0 +END) + +CASE + WHEN (project_event_id IS NOT NULL) THEN 1 + ELSE 0 +END) = 1)) +); + + +-- +-- Name: paliad_schema_migrations; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.paliad_schema_migrations ( + version bigint NOT NULL, + dirty boolean NOT NULL +); + + +-- +-- Name: paliadin_turns; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.paliadin_turns ( + turn_id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + session_id text NOT NULL, + started_at timestamp with time zone DEFAULT now() NOT NULL, + finished_at timestamp with time zone, + duration_ms integer, + user_message text NOT NULL, + response text, + response_tokens integer, + used_tools text[] DEFAULT '{}'::text[] NOT NULL, + rows_seen integer[] DEFAULT '{}'::integer[] NOT NULL, + chip_count integer DEFAULT 0 NOT NULL, + abandoned boolean DEFAULT false NOT NULL, + page_origin text, + error_code text, + classifier_tag text, + context jsonb, + aichat_conversation_id uuid +); + + +-- +-- Name: TABLE paliadin_turns; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.paliadin_turns IS 'Per-turn audit log for Paliadin (in-app AI). PoC variant stores full prompt + response — production v1 will swap to hash-only. Powers /admin/paliadin dashboard. Design: docs/design-paliadin-2026-05-07.md §0.5.6.'; + + +-- +-- Name: COLUMN paliadin_turns.context; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.paliadin_turns.context IS 'Structured page-context payload from the inline widget: route_name + primary_entity_type + primary_entity_id + user_selection_text + view_mode + filter_summary. NULL for legacy turns (PoC standalone page predates the structured payload — page_origin is the only field they carry). Design: docs/design-paliadin-inline-2026-05-08.md §4.'; + + +-- +-- Name: COLUMN paliadin_turns.aichat_conversation_id; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.paliadin_turns.aichat_conversation_id IS 'Aichat backend conversation id (t-paliad-235). Set when the streaming /chat/turn/stream done frame arrives, or when the recovery endpoint asks aichat to disambiguate which conversation this turn lives in. NULL for legacy backend turns and for aichat turns that errored before the conversation id was resolved.'; + + +-- +-- Name: parties; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.parties ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + name text NOT NULL, + role text, + representative text, + contact_info jsonb DEFAULT '{}'::jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid NOT NULL +); + + +-- +-- Name: partner_unit_events; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.partner_unit_events ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + partner_unit_id uuid, + actor_id uuid NOT NULL, + event_type text NOT NULL, + unit_name text NOT NULL, + payload jsonb DEFAULT '{}'::jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT partner_unit_events_event_type_check CHECK ((event_type = ANY (ARRAY['created'::text, 'updated'::text, 'deleted'::text, 'member_added'::text, 'member_removed'::text]))) +); + + +-- +-- Name: partner_unit_members; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.partner_unit_members ( + partner_unit_id uuid NOT NULL, + user_id uuid NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + unit_role text DEFAULT 'attorney'::text NOT NULL, + CONSTRAINT partner_unit_members_unit_role_check CHECK ((unit_role = ANY (ARRAY['lead'::text, 'attorney'::text, 'senior_pa'::text, 'pa'::text, 'paralegal'::text]))) +); + + +-- +-- Name: partner_units; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.partner_units ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + name text NOT NULL, + lead_user_id uuid, + office text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT partner_units_office_check CHECK ((office = ANY (ARRAY['munich'::text, 'duesseldorf'::text, 'hamburg'::text, 'amsterdam'::text, 'london'::text, 'paris'::text, 'milan'::text, 'madrid'::text]))) +); + + +-- +-- Name: policy_audit_log; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.policy_audit_log ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + actor_id uuid NOT NULL, + event_type text NOT NULL, + scope_type text NOT NULL, + project_id uuid, + partner_unit_id uuid, + scope_name text NOT NULL, + entity_type text NOT NULL, + lifecycle_event text NOT NULL, + old_required_role text, + new_required_role text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT policy_audit_log_entity_type_check CHECK ((entity_type = ANY (ARRAY['deadline'::text, 'appointment'::text]))), + CONSTRAINT policy_audit_log_event_type_check CHECK ((event_type = ANY (ARRAY['approval_policy_set'::text, 'approval_policy_cleared'::text]))), + CONSTRAINT policy_audit_log_lifecycle_event_check CHECK ((lifecycle_event = ANY (ARRAY['create'::text, 'update'::text, 'complete'::text, 'delete'::text]))), + CONSTRAINT policy_audit_log_scope_type_check CHECK ((scope_type = ANY (ARRAY['project'::text, 'unit'::text]))), + CONSTRAINT policy_audit_log_scope_xor CHECK ((((scope_type = 'project'::text) AND (project_id IS NOT NULL) AND (partner_unit_id IS NULL)) OR ((scope_type = 'unit'::text) AND (partner_unit_id IS NOT NULL) AND (project_id IS NULL)) OR ((scope_type = 'project'::text) AND (project_id IS NULL) AND (partner_unit_id IS NULL)) OR ((scope_type = 'unit'::text) AND (partner_unit_id IS NULL) AND (project_id IS NULL)))) +); + + +-- +-- Name: TABLE policy_audit_log; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.policy_audit_log IS 'Audit trail for paliad.approval_policies CRUD (t-paliad-154). Surfaces on /admin/audit-log only — not on per-project /verlauf, per design Q8 lock-in. Unioned by services.AuditService alongside project_events / partner_unit_events / caldav_sync_log / reminder_log.'; + + +-- +-- Name: proceeding_types_id_seq; Type: SEQUENCE; Schema: paliad; Owner: - +-- + +CREATE SEQUENCE paliad.proceeding_types_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: proceeding_types_id_seq; Type: SEQUENCE OWNED BY; Schema: paliad; Owner: - +-- + +ALTER SEQUENCE paliad.proceeding_types_id_seq OWNED BY paliad.proceeding_types.id; + + +-- +-- Name: proceeding_types_pre_093; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.proceeding_types_pre_093 ( + id integer, + code text, + name text, + description text, + jurisdiction text, + category text, + default_color text, + sort_order integer, + is_active boolean, + name_en text, + display_order integer, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE proceeding_types_pre_093; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.proceeding_types_pre_093 IS 'Snapshot of the 7 litigation-category paliad.proceeding_types rows (INF, REV, CCR, APM, APP, AMD, ZPO_CIVIL) before mig 093 dropped them. Source-of-truth for the down migration; persists post-drop as the permanent audit record of the Pipeline-A proceeding inventory. Drop with a focused follow-up after the Phase 3 cleanup is verified in prod.'; + + +-- +-- Name: proceeding_types_pre_096; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.proceeding_types_pre_096 ( + id integer, + code text, + name text, + description text, + jurisdiction text, + category text, + default_color text, + sort_order integer, + is_active boolean, + name_en text, + display_order integer, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE proceeding_types_pre_096; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.proceeding_types_pre_096 IS 'Snapshot of paliad.proceeding_types taken before mig 096 renamed the `code` strings to the lowercase dot-separated taxonomy (t-paliad-206, 2026-05-18). Source-of-truth for the down migration; persists post-rename as the permanent audit record.'; + + +-- +-- Name: project_event_choices; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.project_event_choices ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + project_id uuid NOT NULL, + submission_code text NOT NULL, + choice_kind text NOT NULL, + choice_value text NOT NULL, + created_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_by uuid, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT project_event_choices_choice_kind_check CHECK ((choice_kind = ANY (ARRAY['appellant'::text, 'include_ccr'::text, 'skip'::text]))) +); + + +-- +-- Name: TABLE project_event_choices; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.project_event_choices IS 'Per-event-card user picks scoped to a project. choice_kind ∈ {appellant, include_ccr, skip}. choice_value namespace per kind: appellant=claimant|defendant|both|none; include_ccr=true|false; skip=true|false. Join key submission_code matches paliad.deadline_rules.submission_code (the same key AnchorOverrides uses). UNIQUE(project,submission_code,kind) keeps re-picks idempotent. Audit-logged via paliad.system_audit_log (event_type=project_event_choice.set).'; + + +-- +-- Name: project_events; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.project_events ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + event_type text, + title text NOT NULL, + description text, + event_date timestamp with time zone, + created_by uuid, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + project_id uuid NOT NULL, + timeline_kind text +); + + +-- +-- Name: COLUMN project_events.timeline_kind; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.project_events.timeline_kind IS 'When non-NULL, this audit event also surfaces as a SmartTimeline milestone. NULL keeps the row audit-only. See internal/services/projection_service.go for the value space.'; + + +-- +-- Name: project_partner_units; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.project_partner_units ( + project_id uuid NOT NULL, + partner_unit_id uuid NOT NULL, + derive_unit_roles text[] DEFAULT ARRAY['pa'::text, 'senior_pa'::text] NOT NULL, + derive_grants_authority boolean DEFAULT false NOT NULL, + attached_at timestamp with time zone DEFAULT now() NOT NULL, + attached_by uuid +); + + +-- +-- Name: project_teams; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.project_teams ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + project_id uuid NOT NULL, + user_id uuid NOT NULL, + role text NOT NULL, + inherited boolean DEFAULT false NOT NULL, + added_by uuid, + created_at timestamp with time zone DEFAULT now() NOT NULL, + responsibility text DEFAULT 'member'::text NOT NULL, + CONSTRAINT project_teams_responsibility_check CHECK ((responsibility = ANY (ARRAY['admin'::text, 'lead'::text, 'member'::text, 'observer'::text, 'external'::text]))), + CONSTRAINT project_teams_role_check CHECK ((role = ANY (ARRAY['lead'::text, 'associate'::text, 'pa'::text, 'of_counsel'::text, 'local_counsel'::text, 'expert'::text, 'observer'::text, 'senior_pa'::text]))) +); + + +-- +-- Name: COLUMN project_teams.role; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.project_teams.role IS 'DEPRECATED — split into users.profession + project_teams.responsibility in migration 057 (t-paliad-148). Kept as a shadow column for one release. Drop in follow-up migration 058.'; + + +-- +-- Name: COLUMN project_teams.responsibility; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.project_teams.responsibility IS 'Per-project responsibility. admin = can manage team + roles on this project and descendants (inherited via paliad.effective_project_admin). lead/member open the 4-Augen approval gate; observer/external close it. admin is orthogonal to the approval gate — it does NOT open it by itself.'; + + +-- +-- Name: projects; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.projects ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + type text NOT NULL, + parent_id uuid, + path text NOT NULL, + title text NOT NULL, + reference text, + description text, + status text DEFAULT 'active'::text NOT NULL, + created_by uuid, + industry text, + country text, + billing_reference text, + client_number text, + matter_number text, + netdocuments_url text, + patent_number text, + filing_date date, + grant_date date, + court text, + case_number text, + proceeding_type_id integer, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL, + ai_summary text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + our_side text, + counterclaim_of uuid, + instance_level text, + opponent_code text, + CONSTRAINT projects_instance_level_check CHECK (((instance_level IS NULL) OR (instance_level = ANY (ARRAY['first'::text, 'appeal'::text, 'cassation'::text])))), + CONSTRAINT projects_opponent_code_check CHECK (((opponent_code IS NULL) OR ((opponent_code ~ '^[A-Z0-9-]{1,16}$'::text) AND (type = 'litigation'::text)))), + CONSTRAINT projects_our_side_check CHECK (((our_side IS NULL) OR (our_side = ANY (ARRAY['claimant'::text, 'defendant'::text, 'applicant'::text, 'appellant'::text, 'respondent'::text, 'third_party'::text, 'other'::text])))), + CONSTRAINT projects_type_check CHECK ((type = ANY (ARRAY['client'::text, 'litigation'::text, 'patent'::text, 'case'::text, 'project'::text, 'other'::text]))), + CONSTRAINT projekte_client_number_check CHECK (((client_number IS NULL) OR (client_number ~ '^[0-9]{6}$'::text))), + CONSTRAINT projekte_matter_number_check CHECK (((matter_number IS NULL) OR (matter_number ~ '^[0-9]{6}$'::text))), + CONSTRAINT projekte_parent_self_differs CHECK (((parent_id IS NULL) OR (parent_id <> id))), + CONSTRAINT projekte_status_check CHECK ((status = ANY (ARRAY['active'::text, 'archived'::text, 'closed'::text]))), + CONSTRAINT projekte_type_check CHECK ((type = ANY (ARRAY['client'::text, 'litigation'::text, 'patent'::text, 'case'::text, 'project'::text]))) +); + + +-- +-- Name: COLUMN projects.our_side; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.projects.our_side IS 'Which side the firm represents on this case project (renamed in the UI to "Client Role" / "Mandantenrolle" — t-paliad-222 / m/paliad#47). Allowed sub-roles, grouped at display time: Active (claimant, applicant, appellant); Reactive (defendant, respondent); Third Party / Other (third_party, other). NULL = unknown. The form hides the field on non-case project types. Drives the Fristenrechner Determinator perspective chip — Active group → claimant-perspective, Reactive → defendant-perspective, Third Party / Other → null (chip free-pick).'; + + +-- +-- Name: COLUMN projects.counterclaim_of; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.projects.counterclaim_of IS 'When non-NULL this project is the CCR (counterclaim) filed against the referenced parent project. parent_id continues to govern the project tree (CCR is placed as a sibling under the same patent — see ProjectService.CreateCounterclaim). ON DELETE SET NULL keeps the CCR row alive when the parent is hard-deleted (rare; default is archival) so the audit trail survives.'; + + +-- +-- Name: COLUMN projects.instance_level; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.projects.instance_level IS 'Procedural instance the project sits at: first | appeal | cassation. NULL = unset / not applicable. Combined with proceeding_type.code + jurisdiction by FristenrechnerService to pick the effective proceeding code (e.g. DE_INF + appeal → DE_INF_OLG). See design-fristen-phase2-2026-05-15.md §2.7, §7.'; + + +-- +-- Name: COLUMN projects.opponent_code; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.projects.opponent_code IS 'Short slug for the opposing party on a litigation project (uppercase letters, digits, dashes, max 16 chars). Used as the middle segment when BuildProjectCode walks the ancestor tree to assemble a dotted project code — e.g. EXMPL.OPNT.567.INF.CFI (t-paliad-222 / m/paliad#50). NULL = segment skipped silently. Only meaningful on type=''litigation'' rows; the CHECK enforces that pairing.'; + + +-- +-- Name: projects_pre_094; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.projects_pre_094 ( + id uuid, + type text, + parent_id uuid, + path text, + title text, + reference text, + description text, + status text, + created_by uuid, + industry text, + country text, + billing_reference text, + client_number text, + matter_number text, + netdocuments_url text, + patent_number text, + filing_date date, + grant_date date, + court text, + case_number text, + proceeding_type_id integer, + metadata jsonb, + ai_summary text, + created_at timestamp with time zone, + updated_at timestamp with time zone, + our_side text, + counterclaim_of uuid, + instance_level text, + snapshotted_at timestamp with time zone +); + + +-- +-- Name: TABLE projects_pre_094; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.projects_pre_094 IS 'Snapshot of paliad.projects rows that had a client_number or matter_number set before mig 094 tightened the CHECK from 7-digit to 6-digit. The 094 UPDATE NULL-ed those values out because they were leftover 7-digit test data. Persists as the permanent audit anchor; the down migration restores from it.'; + + +-- +-- Name: reminder_log; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.reminder_log ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + deadline_id uuid, + user_id uuid NOT NULL, + reminder_type text NOT NULL, + sent_at timestamp with time zone DEFAULT now() NOT NULL, + slot text, + slot_date date, + CONSTRAINT reminder_log_reminder_type_check CHECK ((reminder_type = ANY (ARRAY['overdue'::text, 'tomorrow'::text, 'weekly'::text, 'morning_digest'::text, 'evening_digest'::text]))), + CONSTRAINT reminder_log_slot_check CHECK (((slot IS NULL) OR (slot = ANY (ARRAY['morning'::text, 'evening'::text])))) +); + + +-- +-- Name: submission_drafts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.submission_drafts ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + project_id uuid, + submission_code text NOT NULL, + user_id uuid NOT NULL, + name text NOT NULL, + variables jsonb DEFAULT '{}'::jsonb NOT NULL, + last_exported_at timestamp with time zone, + last_exported_sha text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + selected_parties uuid[] DEFAULT '{}'::uuid[] NOT NULL, + last_imported_at timestamp with time zone, + language text DEFAULT 'de'::text NOT NULL, + CONSTRAINT submission_drafts_language_check CHECK ((language = ANY (ARRAY['de'::text, 'en'::text]))) +); + + +-- +-- Name: TABLE submission_drafts; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.submission_drafts IS 't-paliad-238: per-(project, submission_code, user) named drafts for the dedicated Submissions/Schriftsätze page. Each row holds the lawyer-edited variable overrides for the .docx export. Audit rows live in paliad.system_audit_log + paliad.project_events.'; + + +-- +-- Name: COLUMN submission_drafts.selected_parties; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.submission_drafts.selected_parties IS 't-paliad-277: party IDs (paliad.parties) the lawyer has chosen to mention in this submission. Empty array = include every party on the project (backward-compat default). Non-empty = restrict to subset, grouped by role.'; + + +-- +-- Name: COLUMN submission_drafts.last_imported_at; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.submission_drafts.last_imported_at IS 't-paliad-277: timestamp of the last "Aus Projekt importieren" click — surfaced next to the button so the lawyer can see staleness at a glance. NULL = never imported.'; + + +-- +-- Name: COLUMN submission_drafts.language; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.submission_drafts.language IS 't-paliad-276: output language for the generated .docx. ''de'' or ''en''. Drives template variant selection ({code}.{lang}.docx fallback chain) and language-aware variable resolution.'; + + +-- +-- Name: system_audit_log; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.system_audit_log ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + event_type text NOT NULL, + actor_id uuid, + actor_email text NOT NULL, + scope text NOT NULL, + scope_root uuid, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT system_audit_log_scope_check CHECK ((scope = ANY (ARRAY['org'::text, 'project'::text, 'personal'::text]))) +); + + +-- +-- Name: TABLE system_audit_log; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TABLE paliad.system_audit_log IS 'Org-wide / scope-spanning audit events. 6th source of AuditService union. Generic event_type + metadata jsonb. Initial users: data-export audit chain (t-paliad-214). Audit rows persist forever; artifact retention is separate.'; + + +-- +-- Name: COLUMN system_audit_log.actor_email; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.system_audit_log.actor_email IS 'Captured at write time so the audit row survives user deletion (actor_id FK uses ON DELETE SET NULL).'; + + +-- +-- Name: COLUMN system_audit_log.scope_root; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.system_audit_log.scope_root IS 'project_id for scope=project; NULL otherwise. Not a hard FK so audit survives project deletion.'; + + +-- +-- Name: user_caldav_config; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_caldav_config ( + user_id uuid NOT NULL, + url text NOT NULL, + username text NOT NULL, + password_encrypted bytea NOT NULL, + calendar_path text DEFAULT ''::text NOT NULL, + enabled boolean DEFAULT true NOT NULL, + last_sync_at timestamp with time zone, + last_sync_error text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + supports_mkcalendar boolean, + mkcalendar_probed_at timestamp with time zone +); + + +-- +-- Name: user_calendar_bindings; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_calendar_bindings ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + calendar_path text NOT NULL, + display_name text DEFAULT ''::text NOT NULL, + scope_kind text NOT NULL, + scope_id uuid, + include_personal boolean DEFAULT false NOT NULL, + enabled boolean DEFAULT true NOT NULL, + last_sync_at timestamp with time zone, + last_sync_error text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT user_calendar_bindings_scope_id_chk CHECK ((((scope_kind = ANY (ARRAY['all_visible'::text, 'personal_only'::text])) AND (scope_id IS NULL)) OR ((scope_kind <> ALL (ARRAY['all_visible'::text, 'personal_only'::text])) AND (scope_id IS NOT NULL)))), + CONSTRAINT user_calendar_bindings_scope_kind_chk CHECK ((scope_kind = ANY (ARRAY['all_visible'::text, 'personal_only'::text, 'project'::text, 'client'::text, 'litigation'::text, 'patent'::text, 'case'::text]))) +); + + +-- +-- Name: user_card_layouts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_card_layouts ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + name text NOT NULL, + is_default boolean DEFAULT false NOT NULL, + layout_json jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: user_dashboard_layouts; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_dashboard_layouts ( + user_id uuid NOT NULL, + layout_json jsonb NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: user_pinned_projects; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_pinned_projects ( + user_id uuid NOT NULL, + project_id uuid NOT NULL, + pinned_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: user_views; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.user_views ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + slug text NOT NULL, + name text NOT NULL, + icon text, + filter_spec jsonb NOT NULL, + render_spec jsonb NOT NULL, + sort_order integer DEFAULT 0 NOT NULL, + show_count boolean DEFAULT false NOT NULL, + last_used_at timestamp with time zone, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: users; Type: TABLE; Schema: paliad; Owner: - +-- + +CREATE TABLE paliad.users ( + id uuid NOT NULL, + email text NOT NULL, + display_name text DEFAULT ''::text NOT NULL, + office text NOT NULL, + practice_group text, + job_title text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + lang text DEFAULT 'de'::text NOT NULL, + email_preferences jsonb DEFAULT '{}'::jsonb NOT NULL, + additional_offices text[] DEFAULT '{}'::text[] NOT NULL, + reminder_morning_time time without time zone DEFAULT '09:00:00'::time without time zone NOT NULL, + reminder_evening_time time without time zone DEFAULT '16:00:00'::time without time zone NOT NULL, + reminder_timezone text DEFAULT 'Europe/Berlin'::text NOT NULL, + global_role text DEFAULT 'standard'::text NOT NULL, + reminder_warning_offset_days integer DEFAULT 7 NOT NULL, + escalation_contact_id uuid, + profession text, + forum_pref text, + inbox_seen_at timestamp with time zone, + CONSTRAINT users_escalation_contact_self_check CHECK (((escalation_contact_id IS NULL) OR (escalation_contact_id <> id))), + CONSTRAINT users_forum_pref_check CHECK (((forum_pref IS NULL) OR (forum_pref = ANY (ARRAY['cms'::text, 'bea'::text, 'posteingang'::text])))), + CONSTRAINT users_global_role_check CHECK ((global_role = ANY (ARRAY['standard'::text, 'global_admin'::text]))), + CONSTRAINT users_job_title_check CHECK (((job_title IS NULL) OR (job_title <> ''::text))), + CONSTRAINT users_office_check CHECK ((office = ANY (ARRAY['munich'::text, 'duesseldorf'::text, 'hamburg'::text, 'amsterdam'::text, 'london'::text, 'paris'::text, 'milan'::text, 'madrid'::text]))), + CONSTRAINT users_profession_check CHECK (((profession IS NULL) OR (profession = ANY (ARRAY['partner'::text, 'of_counsel'::text, 'associate'::text, 'senior_pa'::text, 'pa'::text, 'paralegal'::text])))), + CONSTRAINT users_reminder_warning_offset_check CHECK (((reminder_warning_offset_days >= 1) AND (reminder_warning_offset_days <= 30))) +); + + +-- +-- Name: COLUMN users.profession; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.users.profession IS 'Firm-wide career tier driving the t-paliad-138 approval ladder. NULL = no firm tier (external collaborators, admin accounts). Distinct from job_title (free-text display) and global_role (tool admin gate).'; + + +-- +-- Name: COLUMN users.forum_pref; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.users.forum_pref IS 'Persisted Fristenrechner inbox-channel preference (#15). cms = UPC; bea = national-DE; posteingang = national-DE (slower channel, same forums). NULL = no preference (picker shows everything). URL ?inbox= overrides for the current visit. Set via PATCH /api/me.'; + + +-- +-- Name: COLUMN users.inbox_seen_at; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON COLUMN paliad.users.inbox_seen_at IS 'High-watermark cursor for the /inbox feed. project_events newer than this timestamp are unread for the caller; NULL = never visited (everything unread). Pending approval_requests bypass this column and stay unread until decided.'; + + +-- +-- Name: holidays id; Type: DEFAULT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.holidays ALTER COLUMN id SET DEFAULT nextval('paliad.holidays_id_seq'::regclass); + + +-- +-- Name: proceeding_types id; Type: DEFAULT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.proceeding_types ALTER COLUMN id SET DEFAULT nextval('paliad.proceeding_types_id_seq'::regclass); + + +-- +-- Name: project_events akten_events_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_events + ADD CONSTRAINT akten_events_pkey PRIMARY KEY (id); + + +-- +-- Name: applied_migrations applied_migrations_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.applied_migrations + ADD CONSTRAINT applied_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointment_caldav_targets + ADD CONSTRAINT appointment_caldav_targets_pkey PRIMARY KEY (appointment_id, binding_id); + + +-- +-- Name: approval_policies approval_policies_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_policies + ADD CONSTRAINT approval_policies_pkey PRIMARY KEY (id); + + +-- +-- Name: approval_requests approval_requests_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_pkey PRIMARY KEY (id); + + +-- +-- Name: backups backups_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.backups + ADD CONSTRAINT backups_pkey PRIMARY KEY (id); + + +-- +-- Name: caldav_sync_log caldav_sync_log_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.caldav_sync_log + ADD CONSTRAINT caldav_sync_log_pkey PRIMARY KEY (id); + + +-- +-- Name: checklist_instances checklist_instances_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_instances + ADD CONSTRAINT checklist_instances_pkey PRIMARY KEY (id); + + +-- +-- Name: checklist_shares checklist_shares_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_pkey PRIMARY KEY (id); + + +-- +-- Name: checklist_feedback checklisten_feedback_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_feedback + ADD CONSTRAINT checklisten_feedback_pkey PRIMARY KEY (id); + + +-- +-- Name: checklists checklists_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklists + ADD CONSTRAINT checklists_pkey PRIMARY KEY (id); + + +-- +-- Name: checklists checklists_slug_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklists + ADD CONSTRAINT checklists_slug_key UNIQUE (slug); + + +-- +-- Name: countries countries_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.countries + ADD CONSTRAINT countries_pkey PRIMARY KEY (code); + + +-- +-- Name: courts courts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.courts + ADD CONSTRAINT courts_pkey PRIMARY KEY (id); + + +-- +-- Name: deadline_concept_event_types deadline_concept_event_types_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_concept_event_types + ADD CONSTRAINT deadline_concept_event_types_pkey PRIMARY KEY (concept_id, event_type_id); + + +-- +-- Name: deadline_concepts deadline_concepts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_concepts + ADD CONSTRAINT deadline_concepts_pkey PRIMARY KEY (id); + + +-- +-- Name: deadline_concepts deadline_concepts_slug_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_concepts + ADD CONSTRAINT deadline_concepts_slug_key UNIQUE (slug); + + +-- +-- Name: deadline_event_types deadline_event_types_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_event_types + ADD CONSTRAINT deadline_event_types_pkey PRIMARY KEY (deadline_id, event_type_id); + + +-- +-- Name: deadline_rule_audit deadline_rule_audit_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rule_audit + ADD CONSTRAINT deadline_rule_audit_pkey PRIMARY KEY (id); + + +-- +-- Name: deadline_rule_backfill_orphans deadline_rule_backfill_orphans_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rule_backfill_orphans + ADD CONSTRAINT deadline_rule_backfill_orphans_pkey PRIMARY KEY (id); + + +-- +-- Name: deadline_rules deadline_rules_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_pkey PRIMARY KEY (id); + + +-- +-- Name: documents dokumente_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.documents + ADD CONSTRAINT dokumente_pkey PRIMARY KEY (id); + + +-- +-- Name: email_broadcasts email_broadcasts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_broadcasts + ADD CONSTRAINT email_broadcasts_pkey PRIMARY KEY (id); + + +-- +-- Name: email_template_versions email_template_versions_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_template_versions + ADD CONSTRAINT email_template_versions_pkey PRIMARY KEY (id); + + +-- +-- Name: email_templates email_templates_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_templates + ADD CONSTRAINT email_templates_pkey PRIMARY KEY (key, lang); + + +-- +-- Name: event_categories event_categories_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_categories + ADD CONSTRAINT event_categories_pkey PRIMARY KEY (id); + + +-- +-- Name: event_categories event_categories_slug_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_categories + ADD CONSTRAINT event_categories_slug_key UNIQUE (slug); + + +-- +-- Name: event_category_concepts event_category_concepts_event_category_id_concept_id_procee_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_category_concepts + ADD CONSTRAINT event_category_concepts_event_category_id_concept_id_procee_key UNIQUE NULLS NOT DISTINCT (event_category_id, concept_id, proceeding_type_code); + + +-- +-- Name: event_category_concepts event_category_concepts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_category_concepts + ADD CONSTRAINT event_category_concepts_pkey PRIMARY KEY (id); + + +-- +-- Name: event_types event_types_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_types + ADD CONSTRAINT event_types_pkey PRIMARY KEY (id); + + +-- +-- Name: firm_dashboard_default firm_dashboard_default_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.firm_dashboard_default + ADD CONSTRAINT firm_dashboard_default_pkey PRIMARY KEY (id); + + +-- +-- Name: deadlines fristen_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT fristen_pkey PRIMARY KEY (id); + + +-- +-- Name: court_feedback gerichte_feedback_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.court_feedback + ADD CONSTRAINT gerichte_feedback_pkey PRIMARY KEY (id); + + +-- +-- Name: holidays holidays_date_name_country_regime_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.holidays + ADD CONSTRAINT holidays_date_name_country_regime_key UNIQUE (date, name, country, regime); + + +-- +-- Name: holidays holidays_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.holidays + ADD CONSTRAINT holidays_pkey PRIMARY KEY (id); + + +-- +-- Name: invitations invitations_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.invitations + ADD CONSTRAINT invitations_pkey PRIMARY KEY (id); + + +-- +-- Name: link_feedback link_feedback_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.link_feedback + ADD CONSTRAINT link_feedback_pkey PRIMARY KEY (id); + + +-- +-- Name: link_suggestions link_suggestions_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.link_suggestions + ADD CONSTRAINT link_suggestions_pkey PRIMARY KEY (id); + + +-- +-- Name: notes notizen_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_pkey PRIMARY KEY (id); + + +-- +-- Name: paliad_schema_migrations paliad_schema_migrations_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.paliad_schema_migrations + ADD CONSTRAINT paliad_schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: paliadin_turns paliadin_turns_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.paliadin_turns + ADD CONSTRAINT paliadin_turns_pkey PRIMARY KEY (turn_id); + + +-- +-- Name: parties parteien_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.parties + ADD CONSTRAINT parteien_pkey PRIMARY KEY (id); + + +-- +-- Name: partner_unit_events partner_unit_events_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_events + ADD CONSTRAINT partner_unit_events_pkey PRIMARY KEY (id); + + +-- +-- Name: partner_unit_members partner_unit_members_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_members + ADD CONSTRAINT partner_unit_members_pkey PRIMARY KEY (partner_unit_id, user_id); + + +-- +-- Name: partner_units partner_units_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_units + ADD CONSTRAINT partner_units_pkey PRIMARY KEY (id); + + +-- +-- Name: policy_audit_log policy_audit_log_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.policy_audit_log + ADD CONSTRAINT policy_audit_log_pkey PRIMARY KEY (id); + + +-- +-- Name: proceeding_types proceeding_types_code_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.proceeding_types + ADD CONSTRAINT proceeding_types_code_key UNIQUE (code); + + +-- +-- Name: proceeding_types proceeding_types_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.proceeding_types + ADD CONSTRAINT proceeding_types_pkey PRIMARY KEY (id); + + +-- +-- Name: project_event_choices project_event_choices_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_event_choices + ADD CONSTRAINT project_event_choices_pkey PRIMARY KEY (id); + + +-- +-- Name: project_event_choices project_event_choices_project_id_submission_code_choice_kin_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_event_choices + ADD CONSTRAINT project_event_choices_project_id_submission_code_choice_kin_key UNIQUE (project_id, submission_code, choice_kind); + + +-- +-- Name: project_partner_units project_partner_units_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_partner_units + ADD CONSTRAINT project_partner_units_pkey PRIMARY KEY (project_id, partner_unit_id); + + +-- +-- Name: project_teams projekt_teams_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_teams + ADD CONSTRAINT projekt_teams_pkey PRIMARY KEY (id); + + +-- +-- Name: project_teams projekt_teams_projekt_id_user_id_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_teams + ADD CONSTRAINT projekt_teams_projekt_id_user_id_key UNIQUE (project_id, user_id); + + +-- +-- Name: projects projekte_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.projects + ADD CONSTRAINT projekte_pkey PRIMARY KEY (id); + + +-- +-- Name: reminder_log reminder_log_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.reminder_log + ADD CONSTRAINT reminder_log_pkey PRIMARY KEY (id); + + +-- +-- Name: submission_drafts submission_drafts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.submission_drafts + ADD CONSTRAINT submission_drafts_pkey PRIMARY KEY (id); + + +-- +-- Name: submission_drafts submission_drafts_unique_per_user; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.submission_drafts + ADD CONSTRAINT submission_drafts_unique_per_user UNIQUE (project_id, submission_code, user_id, name); + + +-- +-- Name: system_audit_log system_audit_log_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.system_audit_log + ADD CONSTRAINT system_audit_log_pkey PRIMARY KEY (id); + + +-- +-- Name: appointments termine_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT termine_pkey PRIMARY KEY (id); + + +-- +-- Name: trigger_events trigger_events_code_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.trigger_events + ADD CONSTRAINT trigger_events_code_key UNIQUE (code); + + +-- +-- Name: trigger_events trigger_events_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.trigger_events + ADD CONSTRAINT trigger_events_pkey PRIMARY KEY (id); + + +-- +-- Name: user_caldav_config user_caldav_config_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_caldav_config + ADD CONSTRAINT user_caldav_config_pkey PRIMARY KEY (user_id); + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_calendar_bindings + ADD CONSTRAINT user_calendar_bindings_pkey PRIMARY KEY (id); + + +-- +-- Name: user_card_layouts user_card_layouts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_card_layouts + ADD CONSTRAINT user_card_layouts_pkey PRIMARY KEY (id); + + +-- +-- Name: user_card_layouts user_card_layouts_user_id_name_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_card_layouts + ADD CONSTRAINT user_card_layouts_user_id_name_key UNIQUE (user_id, name); + + +-- +-- Name: user_dashboard_layouts user_dashboard_layouts_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_dashboard_layouts + ADD CONSTRAINT user_dashboard_layouts_pkey PRIMARY KEY (user_id); + + +-- +-- Name: user_pinned_projects user_pinned_projects_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_pinned_projects + ADD CONSTRAINT user_pinned_projects_pkey PRIMARY KEY (user_id, project_id); + + +-- +-- Name: user_views user_views_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_views + ADD CONSTRAINT user_views_pkey PRIMARY KEY (id); + + +-- +-- Name: user_views user_views_user_id_slug_key; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_views + ADD CONSTRAINT user_views_user_id_slug_key UNIQUE (user_id, slug); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: appointment_caldav_targets_binding_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX appointment_caldav_targets_binding_idx ON paliad.appointment_caldav_targets USING btree (binding_id); + + +-- +-- Name: appointment_caldav_targets_uid_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX appointment_caldav_targets_uid_idx ON paliad.appointment_caldav_targets USING btree (caldav_uid); + + +-- +-- Name: appointments_approval_status_pending_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX appointments_approval_status_pending_idx ON paliad.appointments USING btree (approval_status) WHERE (approval_status = 'pending'::text); + + +-- +-- Name: appointments_deadline_rule_id_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX appointments_deadline_rule_id_idx ON paliad.appointments USING btree (deadline_rule_id) WHERE (deadline_rule_id IS NOT NULL); + + +-- +-- Name: approval_policies_project_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_policies_project_idx ON paliad.approval_policies USING btree (project_id); + + +-- +-- Name: approval_policies_project_unique; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX approval_policies_project_unique ON paliad.approval_policies USING btree (project_id, entity_type, lifecycle_event) WHERE (project_id IS NOT NULL); + + +-- +-- Name: approval_policies_unit_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_policies_unit_idx ON paliad.approval_policies USING btree (partner_unit_id) WHERE (partner_unit_id IS NOT NULL); + + +-- +-- Name: approval_policies_unit_unique; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX approval_policies_unit_unique ON paliad.approval_policies USING btree (partner_unit_id, entity_type, lifecycle_event) WHERE (partner_unit_id IS NOT NULL); + + +-- +-- Name: approval_requests_agent_turn_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_agent_turn_idx ON paliad.approval_requests USING btree (agent_turn_id) WHERE (agent_turn_id IS NOT NULL); + + +-- +-- Name: approval_requests_entity_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_entity_idx ON paliad.approval_requests USING btree (entity_type, entity_id); + + +-- +-- Name: approval_requests_pending_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_pending_idx ON paliad.approval_requests USING btree (status, requested_at) WHERE (status = 'pending'::text); + + +-- +-- Name: approval_requests_previous_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_previous_idx ON paliad.approval_requests USING btree (previous_request_id) WHERE (previous_request_id IS NOT NULL); + + +-- +-- Name: approval_requests_project_status_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_project_status_idx ON paliad.approval_requests USING btree (project_id, status); + + +-- +-- Name: approval_requests_requested_by_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX approval_requests_requested_by_idx ON paliad.approval_requests USING btree (requested_by, status); + + +-- +-- Name: backups_kind_status_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX backups_kind_status_idx ON paliad.backups USING btree (kind, status); + + +-- +-- Name: backups_started_at_desc_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX backups_started_at_desc_idx ON paliad.backups USING btree (started_at DESC); + + +-- +-- Name: caldav_sync_log_binding_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX caldav_sync_log_binding_idx ON paliad.caldav_sync_log USING btree (binding_id, occurred_at DESC) WHERE (binding_id IS NOT NULL); + + +-- +-- Name: caldav_sync_log_user_time_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX caldav_sync_log_user_time_idx ON paliad.caldav_sync_log USING btree (user_id, occurred_at DESC); + + +-- +-- Name: checklist_instances_created_by_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklist_instances_created_by_idx ON paliad.checklist_instances USING btree (created_by); + + +-- +-- Name: checklist_instances_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklist_instances_projekt_idx ON paliad.checklist_instances USING btree (project_id) WHERE (project_id IS NOT NULL); + + +-- +-- Name: checklist_instances_template_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklist_instances_template_idx ON paliad.checklist_instances USING btree (template_slug); + + +-- +-- Name: checklist_shares_lookup_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklist_shares_lookup_idx ON paliad.checklist_shares USING btree (checklist_id); + + +-- +-- Name: checklist_shares_office_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX checklist_shares_office_uniq ON paliad.checklist_shares USING btree (checklist_id, recipient_office) WHERE (recipient_kind = 'office'::text); + + +-- +-- Name: checklist_shares_partner_unit_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX checklist_shares_partner_unit_uniq ON paliad.checklist_shares USING btree (checklist_id, recipient_partner_unit_id) WHERE (recipient_kind = 'partner_unit'::text); + + +-- +-- Name: checklist_shares_project_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX checklist_shares_project_uniq ON paliad.checklist_shares USING btree (checklist_id, recipient_project_id) WHERE (recipient_kind = 'project'::text); + + +-- +-- Name: checklist_shares_user_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX checklist_shares_user_uniq ON paliad.checklist_shares USING btree (checklist_id, recipient_user_id) WHERE (recipient_kind = 'user'::text); + + +-- +-- Name: checklists_owner_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklists_owner_idx ON paliad.checklists USING btree (owner_id); + + +-- +-- Name: checklists_regime_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklists_regime_idx ON paliad.checklists USING btree (regime); + + +-- +-- Name: checklists_visibility_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX checklists_visibility_idx ON paliad.checklists USING btree (visibility) WHERE (visibility = ANY (ARRAY['firm'::text, 'global'::text])); + + +-- +-- Name: courts_country_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX courts_country_idx ON paliad.courts USING btree (country); + + +-- +-- Name: courts_court_type_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX courts_court_type_idx ON paliad.courts USING btree (court_type); + + +-- +-- Name: courts_regime_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX courts_regime_idx ON paliad.courts USING btree (regime) WHERE (regime IS NOT NULL); + + +-- +-- Name: deadline_concept_event_types_event_type; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_concept_event_types_event_type ON paliad.deadline_concept_event_types USING btree (event_type_id); + + +-- +-- Name: deadline_concept_event_types_one_default_per_jur; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX deadline_concept_event_types_one_default_per_jur ON paliad.deadline_concept_event_types USING btree (concept_id, jurisdiction) WHERE (is_default = true); + + +-- +-- Name: deadline_concepts_aliases; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_concepts_aliases ON paliad.deadline_concepts USING gin (aliases); + + +-- +-- Name: deadline_concepts_trgm_de; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_concepts_trgm_de ON paliad.deadline_concepts USING gin (name_de public.gin_trgm_ops); + + +-- +-- Name: deadline_concepts_trgm_en; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_concepts_trgm_en ON paliad.deadline_concepts USING gin (name_en public.gin_trgm_ops); + + +-- +-- Name: deadline_event_types_event_type_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_event_types_event_type_idx ON paliad.deadline_event_types USING btree (event_type_id); + + +-- +-- Name: deadline_rule_audit_changed_at_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_audit_changed_at_idx ON paliad.deadline_rule_audit USING btree (changed_at DESC); + + +-- +-- Name: deadline_rule_audit_changed_by_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_audit_changed_by_idx ON paliad.deadline_rule_audit USING btree (changed_by) WHERE (changed_by IS NOT NULL); + + +-- +-- Name: deadline_rule_audit_pending_export_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_audit_pending_export_idx ON paliad.deadline_rule_audit USING btree (changed_at DESC) WHERE (migration_exported = false); + + +-- +-- Name: deadline_rule_audit_rule_id_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_audit_rule_id_idx ON paliad.deadline_rule_audit USING btree (rule_id, changed_at DESC); + + +-- +-- Name: deadline_rule_backfill_orphans_deadline_id_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_backfill_orphans_deadline_id_idx ON paliad.deadline_rule_backfill_orphans USING btree (deadline_id); + + +-- +-- Name: deadline_rule_backfill_orphans_unresolved_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rule_backfill_orphans_unresolved_idx ON paliad.deadline_rule_backfill_orphans USING btree (created_at DESC) WHERE (resolved_at IS NULL); + + +-- +-- Name: deadline_rules_concept_id; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_concept_id ON paliad.deadline_rules USING btree (concept_id); + + +-- +-- Name: deadline_rules_is_bilateral; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_is_bilateral ON paliad.deadline_rules USING btree (is_bilateral) WHERE (is_bilateral = true); + + +-- +-- Name: deadline_rules_legal_source; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_legal_source ON paliad.deadline_rules USING btree (legal_source); + + +-- +-- Name: deadline_rules_legal_src_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_legal_src_trgm ON paliad.deadline_rules USING gin (legal_source public.gin_trgm_ops); + + +-- +-- Name: deadline_rules_lifecycle_state_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_lifecycle_state_idx ON paliad.deadline_rules USING btree (lifecycle_state); + + +-- +-- Name: deadline_rules_parent_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_parent_idx ON paliad.deadline_rules USING btree (parent_id); + + +-- +-- Name: deadline_rules_proceeding_type_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_proceeding_type_idx ON paliad.deadline_rules USING btree (proceeding_type_id); + + +-- +-- Name: deadline_rules_spawn_proceeding_type_id_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_spawn_proceeding_type_id_idx ON paliad.deadline_rules USING btree (spawn_proceeding_type_id) WHERE (spawn_proceeding_type_id IS NOT NULL); + + +-- +-- Name: deadline_rules_trigger_event_id_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_rules_trigger_event_id_idx ON paliad.deadline_rules USING btree (trigger_event_id) WHERE (trigger_event_id IS NOT NULL); + + +-- +-- Name: deadline_search_concept_de_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_concept_de_trgm ON paliad.deadline_search USING gin (concept_name_de public.gin_trgm_ops); + + +-- +-- Name: deadline_search_concept_en_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_concept_en_trgm ON paliad.deadline_search USING gin (concept_name_en public.gin_trgm_ops); + + +-- +-- Name: deadline_search_concept_id; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_concept_id ON paliad.deadline_search USING btree (concept_id); + + +-- +-- Name: deadline_search_effective_party; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_effective_party ON paliad.deadline_search USING btree (effective_party); + + +-- +-- Name: deadline_search_legal_source; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_legal_source ON paliad.deadline_search USING btree (legal_source); + + +-- +-- Name: deadline_search_legal_source_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_legal_source_trgm ON paliad.deadline_search USING gin (legal_source public.gin_trgm_ops); + + +-- +-- Name: deadline_search_proc_code; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_proc_code ON paliad.deadline_search USING btree (proceeding_code); + + +-- +-- Name: deadline_search_row_key; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX deadline_search_row_key ON paliad.deadline_search USING btree (row_key); + + +-- +-- Name: deadline_search_rule_code_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_rule_code_trgm ON paliad.deadline_search USING gin (rule_code public.gin_trgm_ops); + + +-- +-- Name: deadline_search_rule_de_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_rule_de_trgm ON paliad.deadline_search USING gin (rule_name_de public.gin_trgm_ops); + + +-- +-- Name: deadline_search_rule_en_trgm; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadline_search_rule_en_trgm ON paliad.deadline_search USING gin (rule_name_en public.gin_trgm_ops); + + +-- +-- Name: deadlines_approval_status_pending_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX deadlines_approval_status_pending_idx ON paliad.deadlines USING btree (approval_status) WHERE (approval_status = 'pending'::text); + + +-- +-- Name: dokumente_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX dokumente_projekt_idx ON paliad.documents USING btree (project_id); + + +-- +-- Name: email_broadcasts_sender_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX email_broadcasts_sender_idx ON paliad.email_broadcasts USING btree (sender_id, sent_at DESC); + + +-- +-- Name: email_broadcasts_sent_at_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX email_broadcasts_sent_at_idx ON paliad.email_broadcasts USING btree (sent_at DESC); + + +-- +-- Name: email_template_versions_key_lang_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX email_template_versions_key_lang_idx ON paliad.email_template_versions USING btree (key, lang, saved_at DESC); + + +-- +-- Name: event_categories_active; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_categories_active ON paliad.event_categories USING btree (is_active); + + +-- +-- Name: event_categories_forums_gin; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_categories_forums_gin ON paliad.event_categories USING gin (forums) WHERE (forums IS NOT NULL); + + +-- +-- Name: event_categories_parent_id; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_categories_parent_id ON paliad.event_categories USING btree (parent_id); + + +-- +-- Name: event_categories_party_gin; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_categories_party_gin ON paliad.event_categories USING gin (party) WHERE (party IS NOT NULL); + + +-- +-- Name: event_categories_slug; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_categories_slug ON paliad.event_categories USING btree (slug); + + +-- +-- Name: event_category_concepts_category; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_category_concepts_category ON paliad.event_category_concepts USING btree (event_category_id); + + +-- +-- Name: event_category_concepts_concept; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_category_concepts_concept ON paliad.event_category_concepts USING btree (concept_id); + + +-- +-- Name: event_category_concepts_proceeding; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_category_concepts_proceeding ON paliad.event_category_concepts USING btree (proceeding_type_code) WHERE (proceeding_type_code IS NOT NULL); + + +-- +-- Name: event_types_category_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_types_category_idx ON paliad.event_types USING btree (category); + + +-- +-- Name: event_types_firm_slug_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX event_types_firm_slug_idx ON paliad.event_types USING btree (slug) WHERE ((is_firm_wide = true) AND (archived_at IS NULL)); + + +-- +-- Name: event_types_jurisdiction_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_types_jurisdiction_idx ON paliad.event_types USING btree (jurisdiction) WHERE (jurisdiction IS NOT NULL); + + +-- +-- Name: event_types_private_slug_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX event_types_private_slug_idx ON paliad.event_types USING btree (created_by, slug) WHERE ((is_firm_wide = false) AND (archived_at IS NULL)); + + +-- +-- Name: event_types_trigger_event_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX event_types_trigger_event_idx ON paliad.event_types USING btree (trigger_event_id) WHERE (trigger_event_id IS NOT NULL); + + +-- +-- Name: fristen_due_date_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX fristen_due_date_idx ON paliad.deadlines USING btree (due_date); + + +-- +-- Name: fristen_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX fristen_projekt_idx ON paliad.deadlines USING btree (project_id); + + +-- +-- Name: fristen_status_due_date_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX fristen_status_due_date_idx ON paliad.deadlines USING btree (status, due_date); + + +-- +-- Name: holidays_date_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX holidays_date_idx ON paliad.holidays USING btree (date); + + +-- +-- Name: holidays_regime_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX holidays_regime_idx ON paliad.holidays USING btree (regime) WHERE (regime IS NOT NULL); + + +-- +-- Name: invitations_from_user_sent_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX invitations_from_user_sent_idx ON paliad.invitations USING btree (from_user_id, sent_at DESC); + + +-- +-- Name: invitations_to_email_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX invitations_to_email_idx ON paliad.invitations USING btree (lower(to_email)); + + +-- +-- Name: link_suggestions_status_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX link_suggestions_status_idx ON paliad.link_suggestions USING btree (status); + + +-- +-- Name: notizen_akten_event_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX notizen_akten_event_idx ON paliad.notes USING btree (project_event_id) WHERE (project_event_id IS NOT NULL); + + +-- +-- Name: notizen_frist_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX notizen_frist_idx ON paliad.notes USING btree (deadline_id) WHERE (deadline_id IS NOT NULL); + + +-- +-- Name: notizen_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX notizen_projekt_idx ON paliad.notes USING btree (project_id) WHERE (project_id IS NOT NULL); + + +-- +-- Name: notizen_termin_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX notizen_termin_idx ON paliad.notes USING btree (appointment_id) WHERE (appointment_id IS NOT NULL); + + +-- +-- Name: paliadin_turns_classifier_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX paliadin_turns_classifier_idx ON paliad.paliadin_turns USING btree (classifier_tag, started_at DESC) WHERE (classifier_tag IS NOT NULL); + + +-- +-- Name: paliadin_turns_started_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX paliadin_turns_started_idx ON paliad.paliadin_turns USING btree (started_at DESC); + + +-- +-- Name: paliadin_turns_user_started_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX paliadin_turns_user_started_idx ON paliad.paliadin_turns USING btree (user_id, started_at DESC); + + +-- +-- Name: parteien_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX parteien_projekt_idx ON paliad.parties USING btree (project_id); + + +-- +-- Name: partner_unit_events_actor_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_unit_events_actor_idx ON paliad.partner_unit_events USING btree (actor_id, created_at DESC); + + +-- +-- Name: partner_unit_events_time_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_unit_events_time_idx ON paliad.partner_unit_events USING btree (created_at DESC); + + +-- +-- Name: partner_unit_events_unit_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_unit_events_unit_idx ON paliad.partner_unit_events USING btree (partner_unit_id, created_at DESC); + + +-- +-- Name: partner_unit_members_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_unit_members_user_idx ON paliad.partner_unit_members USING btree (user_id); + + +-- +-- Name: partner_units_lead_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_units_lead_idx ON paliad.partner_units USING btree (lead_user_id) WHERE (lead_user_id IS NOT NULL); + + +-- +-- Name: partner_units_office_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX partner_units_office_idx ON paliad.partner_units USING btree (office); + + +-- +-- Name: policy_audit_log_actor_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX policy_audit_log_actor_idx ON paliad.policy_audit_log USING btree (actor_id, created_at DESC); + + +-- +-- Name: policy_audit_log_time_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX policy_audit_log_time_idx ON paliad.policy_audit_log USING btree (created_at DESC); + + +-- +-- Name: project_event_choices_project_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX project_event_choices_project_idx ON paliad.project_event_choices USING btree (project_id); + + +-- +-- Name: project_events_timeline_kind_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX project_events_timeline_kind_idx ON paliad.project_events USING btree (project_id, timeline_kind) WHERE (timeline_kind IS NOT NULL); + + +-- +-- Name: project_partner_units_unit_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX project_partner_units_unit_idx ON paliad.project_partner_units USING btree (partner_unit_id, project_id); + + +-- +-- Name: project_teams_responsibility_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX project_teams_responsibility_idx ON paliad.project_teams USING btree (project_id, responsibility); + + +-- +-- Name: projects_counterclaim_of_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projects_counterclaim_of_idx ON paliad.projects USING btree (counterclaim_of) WHERE (counterclaim_of IS NOT NULL); + + +-- +-- Name: projekt_events_projekt_created_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekt_events_projekt_created_idx ON paliad.project_events USING btree (project_id, created_at DESC); + + +-- +-- Name: projekt_teams_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekt_teams_projekt_idx ON paliad.project_teams USING btree (project_id); + + +-- +-- Name: projekt_teams_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekt_teams_user_idx ON paliad.project_teams USING btree (user_id); + + +-- +-- Name: projekte_client_number_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_client_number_idx ON paliad.projects USING btree (client_number) WHERE (client_number IS NOT NULL); + + +-- +-- Name: projekte_matter_number_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_matter_number_idx ON paliad.projects USING btree (matter_number) WHERE (matter_number IS NOT NULL); + + +-- +-- Name: projekte_parent_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_parent_idx ON paliad.projects USING btree (parent_id); + + +-- +-- Name: projekte_path_prefix_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_path_prefix_idx ON paliad.projects USING btree (path text_pattern_ops); + + +-- +-- Name: projekte_reference_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_reference_idx ON paliad.projects USING btree (reference) WHERE (reference IS NOT NULL); + + +-- +-- Name: projekte_type_status_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX projekte_type_status_idx ON paliad.projects USING btree (type, status); + + +-- +-- Name: reminder_log_dedup_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX reminder_log_dedup_idx ON paliad.reminder_log USING btree (user_id, reminder_type, deadline_id, sent_at DESC); + + +-- +-- Name: reminder_log_slot_dedup_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX reminder_log_slot_dedup_idx ON paliad.reminder_log USING btree (user_id, slot, slot_date) WHERE (slot IS NOT NULL); + + +-- +-- Name: submission_drafts_project_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX submission_drafts_project_user_idx ON paliad.submission_drafts USING btree (project_id, user_id, submission_code, updated_at DESC); + + +-- +-- Name: system_audit_log_actor_id_created_at_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX system_audit_log_actor_id_created_at_idx ON paliad.system_audit_log USING btree (actor_id, created_at DESC); + + +-- +-- Name: system_audit_log_event_type_created_at_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX system_audit_log_event_type_created_at_idx ON paliad.system_audit_log USING btree (event_type, created_at DESC); + + +-- +-- Name: termine_projekt_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX termine_projekt_idx ON paliad.appointments USING btree (project_id) WHERE (project_id IS NOT NULL); + + +-- +-- Name: termine_start_at_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX termine_start_at_idx ON paliad.appointments USING btree (start_at); + + +-- +-- Name: trigger_events_code_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX trigger_events_code_idx ON paliad.trigger_events USING btree (code); + + +-- +-- Name: trigger_events_concept_id; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX trigger_events_concept_id ON paliad.trigger_events USING btree (concept_id); + + +-- +-- Name: user_calendar_bindings_scope_hier_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX user_calendar_bindings_scope_hier_uniq ON paliad.user_calendar_bindings USING btree (user_id, scope_kind, scope_id) WHERE (scope_id IS NOT NULL); + + +-- +-- Name: user_calendar_bindings_scope_root_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX user_calendar_bindings_scope_root_uniq ON paliad.user_calendar_bindings USING btree (user_id, scope_kind) WHERE (scope_id IS NULL); + + +-- +-- Name: user_calendar_bindings_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX user_calendar_bindings_user_idx ON paliad.user_calendar_bindings USING btree (user_id) WHERE enabled; + + +-- +-- Name: user_calendar_bindings_user_path_uniq; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX user_calendar_bindings_user_path_uniq ON paliad.user_calendar_bindings USING btree (user_id, calendar_path); + + +-- +-- Name: user_card_layouts_default_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE UNIQUE INDEX user_card_layouts_default_idx ON paliad.user_card_layouts USING btree (user_id) WHERE (is_default = true); + + +-- +-- Name: user_card_layouts_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX user_card_layouts_user_idx ON paliad.user_card_layouts USING btree (user_id, name); + + +-- +-- Name: user_pinned_projects_user_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX user_pinned_projects_user_idx ON paliad.user_pinned_projects USING btree (user_id, pinned_at DESC); + + +-- +-- Name: user_views_owner_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX user_views_owner_idx ON paliad.user_views USING btree (user_id, sort_order); + + +-- +-- Name: users_office_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX users_office_idx ON paliad.users USING btree (office); + + +-- +-- Name: users_profession_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX users_profession_idx ON paliad.users USING btree (profession); + + +-- +-- Name: users_role_idx; Type: INDEX; Schema: paliad; Owner: - +-- + +CREATE INDEX users_role_idx ON paliad.users USING btree (job_title); + + +-- +-- Name: deadline_rules deadline_rules_audit_aiud; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER deadline_rules_audit_aiud AFTER INSERT OR DELETE OR UPDATE ON paliad.deadline_rules FOR EACH ROW EXECUTE FUNCTION paliad.deadline_rule_audit_trigger(); + + +-- +-- Name: projects projects_no_two_level_ccr; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER projects_no_two_level_ccr BEFORE INSERT OR UPDATE OF counterclaim_of ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projects_no_two_level_ccr(); + + +-- +-- Name: projects projects_proceeding_type_category_check; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER projects_proceeding_type_category_check BEFORE INSERT OR UPDATE OF proceeding_type_id ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projects_proceeding_type_category_check(); + + +-- +-- Name: TRIGGER projects_proceeding_type_category_check ON projects; Type: COMMENT; Schema: paliad; Owner: - +-- + +COMMENT ON TRIGGER projects_proceeding_type_category_check ON paliad.projects IS 'Phase 3 Slice 5 (t-paliad-186) runtime guard for the projects soft-merge — rejects any INSERT/UPDATE that would bind a project to a non-fristenrechner-category proceeding_type. The Go service layer also enforces this with a typed error; this trigger is the defence-in-depth backstop.'; + + +-- +-- Name: projects projects_rewrite_subtree_after; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER projects_rewrite_subtree_after AFTER UPDATE OF path ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projects_rewrite_subtree(); + + +-- +-- Name: projects projects_sync_path_before; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER projects_sync_path_before BEFORE INSERT OR UPDATE OF parent_id ON paliad.projects FOR EACH ROW EXECUTE FUNCTION paliad.projects_sync_path(); + + +-- +-- Name: submission_drafts submission_drafts_set_updated_at; Type: TRIGGER; Schema: paliad; Owner: - +-- + +CREATE TRIGGER submission_drafts_set_updated_at BEFORE UPDATE ON paliad.submission_drafts FOR EACH ROW EXECUTE FUNCTION paliad.tg_set_updated_at(); + + +-- +-- Name: project_events akten_events_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_events + ADD CONSTRAINT akten_events_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: project_events akten_events_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_events + ADD CONSTRAINT akten_events_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_appointment_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointment_caldav_targets + ADD CONSTRAINT appointment_caldav_targets_appointment_id_fkey FOREIGN KEY (appointment_id) REFERENCES paliad.appointments(id) ON DELETE CASCADE; + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_binding_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointment_caldav_targets + ADD CONSTRAINT appointment_caldav_targets_binding_id_fkey FOREIGN KEY (binding_id) REFERENCES paliad.user_calendar_bindings(id) ON DELETE CASCADE; + + +-- +-- Name: appointments appointments_approved_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT appointments_approved_by_fkey FOREIGN KEY (approved_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: appointments appointments_deadline_rule_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT appointments_deadline_rule_id_fkey FOREIGN KEY (deadline_rule_id) REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL; + + +-- +-- Name: appointments appointments_pending_request_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT appointments_pending_request_id_fkey FOREIGN KEY (pending_request_id) REFERENCES paliad.approval_requests(id) ON DELETE SET NULL; + + +-- +-- Name: approval_policies approval_policies_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_policies + ADD CONSTRAINT approval_policies_created_by_fkey FOREIGN KEY (created_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: approval_policies approval_policies_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_policies + ADD CONSTRAINT approval_policies_partner_unit_id_fkey FOREIGN KEY (partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE CASCADE; + + +-- +-- Name: approval_policies approval_policies_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_policies + ADD CONSTRAINT approval_policies_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: approval_requests approval_requests_agent_turn_fk; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_agent_turn_fk FOREIGN KEY (agent_turn_id) REFERENCES paliad.paliadin_turns(turn_id) ON DELETE SET NULL; + + +-- +-- Name: approval_requests approval_requests_decided_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_decided_by_fkey FOREIGN KEY (decided_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: approval_requests approval_requests_previous_request_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_previous_request_id_fkey FOREIGN KEY (previous_request_id) REFERENCES paliad.approval_requests(id) ON DELETE SET NULL; + + +-- +-- Name: approval_requests approval_requests_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: approval_requests approval_requests_requested_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.approval_requests + ADD CONSTRAINT approval_requests_requested_by_fkey FOREIGN KEY (requested_by) REFERENCES paliad.users(id) ON DELETE RESTRICT; + + +-- +-- Name: backups backups_audit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.backups + ADD CONSTRAINT backups_audit_id_fkey FOREIGN KEY (audit_id) REFERENCES paliad.system_audit_log(id) ON DELETE SET NULL; + + +-- +-- Name: backups backups_requested_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.backups + ADD CONSTRAINT backups_requested_by_fkey FOREIGN KEY (requested_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: caldav_sync_log caldav_sync_log_binding_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.caldav_sync_log + ADD CONSTRAINT caldav_sync_log_binding_id_fkey FOREIGN KEY (binding_id) REFERENCES paliad.user_calendar_bindings(id) ON DELETE SET NULL; + + +-- +-- Name: caldav_sync_log caldav_sync_log_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.caldav_sync_log + ADD CONSTRAINT caldav_sync_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: checklist_instances checklist_instances_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_instances + ADD CONSTRAINT checklist_instances_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: checklist_instances checklist_instances_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_instances + ADD CONSTRAINT checklist_instances_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE SET NULL; + + +-- +-- Name: checklist_shares checklist_shares_checklist_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_checklist_id_fkey FOREIGN KEY (checklist_id) REFERENCES paliad.checklists(id) ON DELETE CASCADE; + + +-- +-- Name: checklist_shares checklist_shares_granted_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_granted_by_fkey FOREIGN KEY (granted_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: checklist_shares checklist_shares_recipient_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_recipient_partner_unit_id_fkey FOREIGN KEY (recipient_partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE CASCADE; + + +-- +-- Name: checklist_shares checklist_shares_recipient_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_recipient_project_id_fkey FOREIGN KEY (recipient_project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: checklist_shares checklist_shares_recipient_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklist_shares + ADD CONSTRAINT checklist_shares_recipient_user_id_fkey FOREIGN KEY (recipient_user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: checklists checklists_owner_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklists + ADD CONSTRAINT checklists_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: checklists checklists_promoted_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.checklists + ADD CONSTRAINT checklists_promoted_by_fkey FOREIGN KEY (promoted_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: courts courts_country_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.courts + ADD CONSTRAINT courts_country_fkey FOREIGN KEY (country) REFERENCES paliad.countries(code); + + +-- +-- Name: courts courts_parent_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.courts + ADD CONSTRAINT courts_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES paliad.courts(id); + + +-- +-- Name: deadline_concept_event_types deadline_concept_event_types_concept_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_concept_event_types + ADD CONSTRAINT deadline_concept_event_types_concept_id_fkey FOREIGN KEY (concept_id) REFERENCES paliad.deadline_concepts(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_concept_event_types deadline_concept_event_types_event_type_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_concept_event_types + ADD CONSTRAINT deadline_concept_event_types_event_type_id_fkey FOREIGN KEY (event_type_id) REFERENCES paliad.event_types(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_event_types deadline_event_types_deadline_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_event_types + ADD CONSTRAINT deadline_event_types_deadline_id_fkey FOREIGN KEY (deadline_id) REFERENCES paliad.deadlines(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_event_types deadline_event_types_event_type_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_event_types + ADD CONSTRAINT deadline_event_types_event_type_id_fkey FOREIGN KEY (event_type_id) REFERENCES paliad.event_types(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_rule_audit deadline_rule_audit_changed_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rule_audit + ADD CONSTRAINT deadline_rule_audit_changed_by_fkey FOREIGN KEY (changed_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: deadline_rule_backfill_orphans deadline_rule_backfill_orphans_deadline_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rule_backfill_orphans + ADD CONSTRAINT deadline_rule_backfill_orphans_deadline_id_fkey FOREIGN KEY (deadline_id) REFERENCES paliad.deadlines(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_rule_backfill_orphans deadline_rule_backfill_orphans_resolved_rule_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rule_backfill_orphans + ADD CONSTRAINT deadline_rule_backfill_orphans_resolved_rule_id_fkey FOREIGN KEY (resolved_rule_id) REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL; + + +-- +-- Name: deadline_rules deadline_rules_concept_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_concept_id_fkey FOREIGN KEY (concept_id) REFERENCES paliad.deadline_concepts(id); + + +-- +-- Name: deadline_rules deadline_rules_draft_of_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_draft_of_fkey FOREIGN KEY (draft_of) REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL DEFERRABLE; + + +-- +-- Name: deadline_rules deadline_rules_parent_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL; + + +-- +-- Name: deadline_rules deadline_rules_proceeding_type_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_proceeding_type_id_fkey FOREIGN KEY (proceeding_type_id) REFERENCES paliad.proceeding_types(id) ON DELETE CASCADE; + + +-- +-- Name: deadline_rules deadline_rules_spawn_proceeding_type_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_spawn_proceeding_type_id_fkey FOREIGN KEY (spawn_proceeding_type_id) REFERENCES paliad.proceeding_types(id) ON DELETE SET NULL DEFERRABLE; + + +-- +-- Name: deadline_rules deadline_rules_trigger_event_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadline_rules + ADD CONSTRAINT deadline_rules_trigger_event_id_fkey FOREIGN KEY (trigger_event_id) REFERENCES paliad.trigger_events(id) ON DELETE SET NULL DEFERRABLE; + + +-- +-- Name: deadlines deadlines_approved_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT deadlines_approved_by_fkey FOREIGN KEY (approved_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: deadlines deadlines_pending_request_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT deadlines_pending_request_id_fkey FOREIGN KEY (pending_request_id) REFERENCES paliad.approval_requests(id) ON DELETE SET NULL; + + +-- +-- Name: documents dokumente_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.documents + ADD CONSTRAINT dokumente_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: documents dokumente_uploaded_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.documents + ADD CONSTRAINT dokumente_uploaded_by_fkey FOREIGN KEY (uploaded_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: email_broadcasts email_broadcasts_sender_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_broadcasts + ADD CONSTRAINT email_broadcasts_sender_id_fkey FOREIGN KEY (sender_id) REFERENCES paliad.users(id); + + +-- +-- Name: email_template_versions email_template_versions_saved_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_template_versions + ADD CONSTRAINT email_template_versions_saved_by_fkey FOREIGN KEY (saved_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: email_templates email_templates_updated_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.email_templates + ADD CONSTRAINT email_templates_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: event_categories event_categories_parent_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_categories + ADD CONSTRAINT event_categories_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES paliad.event_categories(id) ON DELETE CASCADE; + + +-- +-- Name: event_category_concepts event_category_concepts_concept_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_category_concepts + ADD CONSTRAINT event_category_concepts_concept_id_fkey FOREIGN KEY (concept_id) REFERENCES paliad.deadline_concepts(id) ON DELETE CASCADE; + + +-- +-- Name: event_category_concepts event_category_concepts_event_category_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_category_concepts + ADD CONSTRAINT event_category_concepts_event_category_id_fkey FOREIGN KEY (event_category_id) REFERENCES paliad.event_categories(id) ON DELETE CASCADE; + + +-- +-- Name: event_types event_types_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.event_types + ADD CONSTRAINT event_types_created_by_fkey FOREIGN KEY (created_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: firm_dashboard_default firm_dashboard_default_updated_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.firm_dashboard_default + ADD CONSTRAINT firm_dashboard_default_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: deadlines fristen_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT fristen_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: deadlines fristen_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT fristen_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: deadlines fristen_rule_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.deadlines + ADD CONSTRAINT fristen_rule_id_fkey FOREIGN KEY (rule_id) REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL; + + +-- +-- Name: holidays holidays_country_fk; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.holidays + ADD CONSTRAINT holidays_country_fk FOREIGN KEY (country) REFERENCES paliad.countries(code); + + +-- +-- Name: invitations invitations_from_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.invitations + ADD CONSTRAINT invitations_from_user_id_fkey FOREIGN KEY (from_user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: notes notizen_akten_event_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_akten_event_id_fkey FOREIGN KEY (project_event_id) REFERENCES paliad.project_events(id) ON DELETE CASCADE; + + +-- +-- Name: notes notizen_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: notes notizen_frist_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_frist_id_fkey FOREIGN KEY (deadline_id) REFERENCES paliad.deadlines(id) ON DELETE CASCADE; + + +-- +-- Name: notes notizen_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: notes notizen_termin_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.notes + ADD CONSTRAINT notizen_termin_id_fkey FOREIGN KEY (appointment_id) REFERENCES paliad.appointments(id) ON DELETE CASCADE; + + +-- +-- Name: paliadin_turns paliadin_turns_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.paliadin_turns + ADD CONSTRAINT paliadin_turns_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id); + + +-- +-- Name: parties parteien_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.parties + ADD CONSTRAINT parteien_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: partner_unit_events partner_unit_events_actor_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_events + ADD CONSTRAINT partner_unit_events_actor_id_fkey FOREIGN KEY (actor_id) REFERENCES auth.users(id); + + +-- +-- Name: partner_unit_events partner_unit_events_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_events + ADD CONSTRAINT partner_unit_events_partner_unit_id_fkey FOREIGN KEY (partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE SET NULL; + + +-- +-- Name: partner_unit_members partner_unit_members_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_members + ADD CONSTRAINT partner_unit_members_partner_unit_id_fkey FOREIGN KEY (partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE CASCADE; + + +-- +-- Name: partner_unit_members partner_unit_members_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_unit_members + ADD CONSTRAINT partner_unit_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: partner_units partner_units_lead_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.partner_units + ADD CONSTRAINT partner_units_lead_user_id_fkey FOREIGN KEY (lead_user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: policy_audit_log policy_audit_log_actor_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.policy_audit_log + ADD CONSTRAINT policy_audit_log_actor_id_fkey FOREIGN KEY (actor_id) REFERENCES paliad.users(id) ON DELETE RESTRICT; + + +-- +-- Name: policy_audit_log policy_audit_log_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.policy_audit_log + ADD CONSTRAINT policy_audit_log_partner_unit_id_fkey FOREIGN KEY (partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE SET NULL; + + +-- +-- Name: policy_audit_log policy_audit_log_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.policy_audit_log + ADD CONSTRAINT policy_audit_log_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE SET NULL; + + +-- +-- Name: project_event_choices project_event_choices_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_event_choices + ADD CONSTRAINT project_event_choices_created_by_fkey FOREIGN KEY (created_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: project_event_choices project_event_choices_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_event_choices + ADD CONSTRAINT project_event_choices_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: project_event_choices project_event_choices_updated_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_event_choices + ADD CONSTRAINT project_event_choices_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: project_partner_units project_partner_units_attached_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_partner_units + ADD CONSTRAINT project_partner_units_attached_by_fkey FOREIGN KEY (attached_by) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: project_partner_units project_partner_units_partner_unit_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_partner_units + ADD CONSTRAINT project_partner_units_partner_unit_id_fkey FOREIGN KEY (partner_unit_id) REFERENCES paliad.partner_units(id) ON DELETE CASCADE; + + +-- +-- Name: project_partner_units project_partner_units_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_partner_units + ADD CONSTRAINT project_partner_units_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: projects projects_counterclaim_of_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.projects + ADD CONSTRAINT projects_counterclaim_of_fkey FOREIGN KEY (counterclaim_of) REFERENCES paliad.projects(id) ON DELETE SET NULL; + + +-- +-- Name: project_teams projekt_teams_added_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_teams + ADD CONSTRAINT projekt_teams_added_by_fkey FOREIGN KEY (added_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: project_teams projekt_teams_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_teams + ADD CONSTRAINT projekt_teams_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: project_teams projekt_teams_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.project_teams + ADD CONSTRAINT projekt_teams_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: projects projekte_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.projects + ADD CONSTRAINT projekte_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: projects projekte_parent_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.projects + ADD CONSTRAINT projekte_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: projects projekte_proceeding_type_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.projects + ADD CONSTRAINT projekte_proceeding_type_id_fkey FOREIGN KEY (proceeding_type_id) REFERENCES paliad.proceeding_types(id) ON DELETE SET NULL; + + +-- +-- Name: reminder_log reminder_log_frist_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.reminder_log + ADD CONSTRAINT reminder_log_frist_id_fkey FOREIGN KEY (deadline_id) REFERENCES paliad.deadlines(id) ON DELETE CASCADE; + + +-- +-- Name: reminder_log reminder_log_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.reminder_log + ADD CONSTRAINT reminder_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: submission_drafts submission_drafts_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.submission_drafts + ADD CONSTRAINT submission_drafts_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: submission_drafts submission_drafts_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.submission_drafts + ADD CONSTRAINT submission_drafts_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: system_audit_log system_audit_log_actor_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.system_audit_log + ADD CONSTRAINT system_audit_log_actor_id_fkey FOREIGN KEY (actor_id) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: appointments termine_created_by_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT termine_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth.users(id) ON DELETE SET NULL; + + +-- +-- Name: appointments termine_projekt_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.appointments + ADD CONSTRAINT termine_projekt_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: user_caldav_config user_caldav_config_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_caldav_config + ADD CONSTRAINT user_caldav_config_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_scope_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_calendar_bindings + ADD CONSTRAINT user_calendar_bindings_scope_id_fkey FOREIGN KEY (scope_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_calendar_bindings + ADD CONSTRAINT user_calendar_bindings_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_card_layouts user_card_layouts_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_card_layouts + ADD CONSTRAINT user_card_layouts_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_dashboard_layouts user_dashboard_layouts_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_dashboard_layouts + ADD CONSTRAINT user_dashboard_layouts_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_pinned_projects user_pinned_projects_project_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_pinned_projects + ADD CONSTRAINT user_pinned_projects_project_id_fkey FOREIGN KEY (project_id) REFERENCES paliad.projects(id) ON DELETE CASCADE; + + +-- +-- Name: user_pinned_projects user_pinned_projects_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_pinned_projects + ADD CONSTRAINT user_pinned_projects_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_views user_views_user_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.user_views + ADD CONSTRAINT user_views_user_id_fkey FOREIGN KEY (user_id) REFERENCES paliad.users(id) ON DELETE CASCADE; + + +-- +-- Name: users users_escalation_contact_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.users + ADD CONSTRAINT users_escalation_contact_id_fkey FOREIGN KEY (escalation_contact_id) REFERENCES paliad.users(id) ON DELETE SET NULL; + + +-- +-- Name: users users_id_fkey; Type: FK CONSTRAINT; Schema: paliad; Owner: - +-- + +ALTER TABLE ONLY paliad.users + ADD CONSTRAINT users_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: appointment_caldav_targets; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.appointment_caldav_targets ENABLE ROW LEVEL SECURITY; + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_self_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointment_caldav_targets_self_delete ON paliad.appointment_caldav_targets FOR DELETE TO authenticated USING ((EXISTS ( SELECT 1 + FROM paliad.user_calendar_bindings b + WHERE ((b.id = appointment_caldav_targets.binding_id) AND (b.user_id = auth.uid()))))); + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_self_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointment_caldav_targets_self_insert ON paliad.appointment_caldav_targets FOR INSERT TO authenticated WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.user_calendar_bindings b + WHERE ((b.id = appointment_caldav_targets.binding_id) AND (b.user_id = auth.uid()))))); + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_self_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointment_caldav_targets_self_select ON paliad.appointment_caldav_targets FOR SELECT TO authenticated USING ((EXISTS ( SELECT 1 + FROM paliad.user_calendar_bindings b + WHERE ((b.id = appointment_caldav_targets.binding_id) AND (b.user_id = auth.uid()))))); + + +-- +-- Name: appointment_caldav_targets appointment_caldav_targets_self_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointment_caldav_targets_self_update ON paliad.appointment_caldav_targets FOR UPDATE TO authenticated USING ((EXISTS ( SELECT 1 + FROM paliad.user_calendar_bindings b + WHERE ((b.id = appointment_caldav_targets.binding_id) AND (b.user_id = auth.uid()))))) WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.user_calendar_bindings b + WHERE ((b.id = appointment_caldav_targets.binding_id) AND (b.user_id = auth.uid()))))); + + +-- +-- Name: appointments; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.appointments ENABLE ROW LEVEL SECURITY; + +-- +-- Name: appointments appointments_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointments_delete ON paliad.appointments FOR DELETE TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: appointments appointments_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointments_insert ON paliad.appointments FOR INSERT TO authenticated WITH CHECK ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: appointments appointments_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointments_select ON paliad.appointments FOR SELECT TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: appointments appointments_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY appointments_update ON paliad.appointments FOR UPDATE TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))) WITH CHECK ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: approval_policies; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.approval_policies ENABLE ROW LEVEL SECURITY; + +-- +-- Name: approval_policies approval_policies_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY approval_policies_select ON paliad.approval_policies FOR SELECT TO authenticated USING (paliad.can_see_project(project_id)); + + +-- +-- Name: approval_policies approval_policies_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY approval_policies_write ON paliad.approval_policies TO authenticated USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))) WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: approval_requests; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.approval_requests ENABLE ROW LEVEL SECURITY; + +-- +-- Name: approval_requests approval_requests_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY approval_requests_all ON paliad.approval_requests TO authenticated USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: backups; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.backups ENABLE ROW LEVEL SECURITY; + +-- +-- Name: backups backups_select_admin; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY backups_select_admin ON paliad.backups FOR SELECT USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: caldav_sync_log; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.caldav_sync_log ENABLE ROW LEVEL SECURITY; + +-- +-- Name: caldav_sync_log caldav_sync_log_self_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY caldav_sync_log_self_select ON paliad.caldav_sync_log FOR SELECT TO authenticated USING ((user_id = auth.uid())); + + +-- +-- Name: checklist_feedback; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.checklist_feedback ENABLE ROW LEVEL SECURITY; + +-- +-- Name: checklist_instances; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.checklist_instances ENABLE ROW LEVEL SECURITY; + +-- +-- Name: checklist_instances checklist_instances_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_instances_delete ON paliad.checklist_instances FOR DELETE TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: checklist_instances checklist_instances_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_instances_insert ON paliad.checklist_instances FOR INSERT TO authenticated WITH CHECK (((created_by = auth.uid()) AND ((project_id IS NULL) OR paliad.can_see_project(project_id)))); + + +-- +-- Name: checklist_instances checklist_instances_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_instances_select ON paliad.checklist_instances FOR SELECT TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: checklist_instances checklist_instances_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_instances_update ON paliad.checklist_instances FOR UPDATE TO authenticated USING ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))) WITH CHECK ((((project_id IS NULL) AND (created_by = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: checklist_shares; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.checklist_shares ENABLE ROW LEVEL SECURITY; + +-- +-- Name: checklist_shares checklist_shares_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_shares_delete ON paliad.checklist_shares FOR DELETE TO authenticated USING (((EXISTS ( SELECT 1 + FROM paliad.checklists c + WHERE ((c.id = checklist_shares.checklist_id) AND (c.owner_id = auth.uid())))) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: checklist_shares checklist_shares_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_shares_insert ON paliad.checklist_shares FOR INSERT TO authenticated WITH CHECK (((EXISTS ( SELECT 1 + FROM paliad.checklists c + WHERE ((c.id = checklist_shares.checklist_id) AND (c.owner_id = auth.uid())))) AND (granted_by = auth.uid()))); + + +-- +-- Name: checklist_shares checklist_shares_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklist_shares_select ON paliad.checklist_shares FOR SELECT TO authenticated USING (((EXISTS ( SELECT 1 + FROM paliad.checklists c + WHERE ((c.id = checklist_shares.checklist_id) AND (c.owner_id = auth.uid())))) OR ((recipient_kind = 'user'::text) AND (recipient_user_id = auth.uid())) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: checklist_feedback checklisten_feedback_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklisten_feedback_insert ON paliad.checklist_feedback FOR INSERT TO authenticated WITH CHECK (true); + + +-- +-- Name: checklists; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.checklists ENABLE ROW LEVEL SECURITY; + +-- +-- Name: checklists checklists_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklists_delete ON paliad.checklists FOR DELETE TO authenticated USING (((owner_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: checklists checklists_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklists_insert ON paliad.checklists FOR INSERT TO authenticated WITH CHECK ((owner_id = auth.uid())); + + +-- +-- Name: checklists checklists_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklists_select ON paliad.checklists FOR SELECT TO authenticated USING (paliad.can_see_checklist(auth.uid(), id)); + + +-- +-- Name: checklists checklists_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY checklists_update ON paliad.checklists FOR UPDATE TO authenticated USING (((owner_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))) WITH CHECK (((owner_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: court_feedback; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.court_feedback ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadline_event_types; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.deadline_event_types ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadline_event_types deadline_event_types_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY deadline_event_types_all ON paliad.deadline_event_types TO authenticated USING (((EXISTS ( SELECT 1 + FROM paliad.deadlines d + WHERE ((d.id = deadline_event_types.deadline_id) AND paliad.can_see_project(d.project_id)))) AND (EXISTS ( SELECT 1 + FROM paliad.event_types et + WHERE ((et.id = deadline_event_types.event_type_id) AND (et.archived_at IS NULL) AND ((et.is_firm_wide = true) OR (et.created_by = auth.uid()))))))) WITH CHECK (((EXISTS ( SELECT 1 + FROM paliad.deadlines d + WHERE ((d.id = deadline_event_types.deadline_id) AND paliad.can_see_project(d.project_id)))) AND (EXISTS ( SELECT 1 + FROM paliad.event_types et + WHERE ((et.id = deadline_event_types.event_type_id) AND (et.archived_at IS NULL) AND ((et.is_firm_wide = true) OR (et.created_by = auth.uid()))))))); + + +-- +-- Name: deadline_rule_audit; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.deadline_rule_audit ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadline_rule_audit deadline_rule_audit_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY deadline_rule_audit_select ON paliad.deadline_rule_audit FOR SELECT USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: deadline_rule_backfill_orphans; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.deadline_rule_backfill_orphans ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadline_rule_backfill_orphans deadline_rule_backfill_orphans_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY deadline_rule_backfill_orphans_select ON paliad.deadline_rule_backfill_orphans FOR SELECT USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: deadline_rules; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.deadline_rules ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadline_rules deadline_rules_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY deadline_rules_select ON paliad.deadline_rules FOR SELECT TO authenticated USING (true); + + +-- +-- Name: deadlines; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.deadlines ENABLE ROW LEVEL SECURITY; + +-- +-- Name: deadlines deadlines_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY deadlines_all ON paliad.deadlines TO authenticated USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: documents; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.documents ENABLE ROW LEVEL SECURITY; + +-- +-- Name: documents documents_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY documents_all ON paliad.documents TO authenticated USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: email_broadcasts; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.email_broadcasts ENABLE ROW LEVEL SECURITY; + +-- +-- Name: email_broadcasts email_broadcasts_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY email_broadcasts_insert ON paliad.email_broadcasts FOR INSERT WITH CHECK ((sender_id = auth.uid())); + + +-- +-- Name: email_broadcasts email_broadcasts_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY email_broadcasts_select ON paliad.email_broadcasts FOR SELECT USING (((sender_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: email_template_versions; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.email_template_versions ENABLE ROW LEVEL SECURITY; + +-- +-- Name: email_templates; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.email_templates ENABLE ROW LEVEL SECURITY; + +-- +-- Name: event_types; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.event_types ENABLE ROW LEVEL SECURITY; + +-- +-- Name: event_types event_types_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY event_types_insert ON paliad.event_types FOR INSERT TO authenticated WITH CHECK ((created_by = auth.uid())); + + +-- +-- Name: event_types event_types_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY event_types_select ON paliad.event_types FOR SELECT TO authenticated USING (((archived_at IS NULL) AND ((is_firm_wide = true) OR (created_by = auth.uid())))); + + +-- +-- Name: event_types event_types_update_admin; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY event_types_update_admin ON paliad.event_types FOR UPDATE TO authenticated USING (((is_firm_wide = true) AND (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: event_types event_types_update_owner; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY event_types_update_owner ON paliad.event_types FOR UPDATE TO authenticated USING ((created_by = auth.uid())) WITH CHECK ((created_by = auth.uid())); + + +-- +-- Name: firm_dashboard_default; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.firm_dashboard_default ENABLE ROW LEVEL SECURITY; + +-- +-- Name: firm_dashboard_default firm_dashboard_default_read; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY firm_dashboard_default_read ON paliad.firm_dashboard_default FOR SELECT USING (true); + + +-- +-- Name: court_feedback gerichte_feedback_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY gerichte_feedback_insert ON paliad.court_feedback FOR INSERT TO authenticated WITH CHECK (true); + + +-- +-- Name: holidays; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.holidays ENABLE ROW LEVEL SECURITY; + +-- +-- Name: holidays holidays_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY holidays_select ON paliad.holidays FOR SELECT TO authenticated USING (true); + + +-- +-- Name: invitations; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.invitations ENABLE ROW LEVEL SECURITY; + +-- +-- Name: link_feedback; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.link_feedback ENABLE ROW LEVEL SECURITY; + +-- +-- Name: link_feedback link_feedback_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY link_feedback_insert ON paliad.link_feedback FOR INSERT TO authenticated WITH CHECK (true); + + +-- +-- Name: link_suggestions; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.link_suggestions ENABLE ROW LEVEL SECURITY; + +-- +-- Name: link_suggestions link_suggestions_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY link_suggestions_insert ON paliad.link_suggestions FOR INSERT TO authenticated WITH CHECK (true); + + +-- +-- Name: notes; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.notes ENABLE ROW LEVEL SECURITY; + +-- +-- Name: notes notes_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY notes_all ON paliad.notes TO authenticated USING (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id)) WITH CHECK (paliad.note_is_visible(project_id, deadline_id, appointment_id, project_event_id)); + + +-- +-- Name: paliadin_turns; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.paliadin_turns ENABLE ROW LEVEL SECURITY; + +-- +-- Name: paliadin_turns paliadin_turns_insert_admin_only; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY paliadin_turns_insert_admin_only ON paliad.paliadin_turns FOR INSERT WITH CHECK (false); + + +-- +-- Name: paliadin_turns paliadin_turns_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY paliadin_turns_select ON paliad.paliadin_turns FOR SELECT USING (((user_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: paliadin_turns paliadin_turns_update_admin_only; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY paliadin_turns_update_admin_only ON paliad.paliadin_turns FOR UPDATE USING (false); + + +-- +-- Name: parties; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.parties ENABLE ROW LEVEL SECURITY; + +-- +-- Name: parties parties_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY parties_all ON paliad.parties TO authenticated USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: partner_unit_events; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.partner_unit_events ENABLE ROW LEVEL SECURITY; + +-- +-- Name: partner_unit_events partner_unit_events_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_unit_events_select ON paliad.partner_unit_events FOR SELECT USING ((auth.uid() IS NOT NULL)); + + +-- +-- Name: partner_unit_events partner_unit_events_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_unit_events_write ON paliad.partner_unit_events FOR INSERT WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: partner_unit_members; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.partner_unit_members ENABLE ROW LEVEL SECURITY; + +-- +-- Name: partner_unit_members partner_unit_members_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_unit_members_select ON paliad.partner_unit_members FOR SELECT TO authenticated USING (true); + + +-- +-- Name: partner_unit_members partner_unit_members_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_unit_members_write ON paliad.partner_unit_members TO authenticated USING (((user_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.job_title = 'admin'::text)))))) WITH CHECK (((user_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.job_title = 'admin'::text)))))); + + +-- +-- Name: partner_units; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.partner_units ENABLE ROW LEVEL SECURITY; + +-- +-- Name: partner_units partner_units_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_units_select ON paliad.partner_units FOR SELECT TO authenticated USING (true); + + +-- +-- Name: partner_units partner_units_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY partner_units_write ON paliad.partner_units TO authenticated USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.job_title = 'admin'::text))))) WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.job_title = 'admin'::text))))); + + +-- +-- Name: policy_audit_log; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.policy_audit_log ENABLE ROW LEVEL SECURITY; + +-- +-- Name: policy_audit_log policy_audit_log_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY policy_audit_log_select ON paliad.policy_audit_log FOR SELECT USING ((auth.uid() IS NOT NULL)); + + +-- +-- Name: policy_audit_log policy_audit_log_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY policy_audit_log_write ON paliad.policy_audit_log FOR INSERT WITH CHECK ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: proceeding_types; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.proceeding_types ENABLE ROW LEVEL SECURITY; + +-- +-- Name: proceeding_types proceeding_types_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY proceeding_types_select ON paliad.proceeding_types FOR SELECT TO authenticated USING (true); + + +-- +-- Name: project_event_choices; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.project_event_choices ENABLE ROW LEVEL SECURITY; + +-- +-- Name: project_event_choices project_event_choices_mutate; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_event_choices_mutate ON paliad.project_event_choices USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: project_event_choices project_event_choices_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_event_choices_select ON paliad.project_event_choices FOR SELECT USING (paliad.can_see_project(project_id)); + + +-- +-- Name: project_events; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.project_events ENABLE ROW LEVEL SECURITY; + +-- +-- Name: project_events project_events_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_events_all ON paliad.project_events TO authenticated USING (paliad.can_see_project(project_id)) WITH CHECK (paliad.can_see_project(project_id)); + + +-- +-- Name: project_partner_units; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.project_partner_units ENABLE ROW LEVEL SECURITY; + +-- +-- Name: project_partner_units project_partner_units_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_partner_units_select ON paliad.project_partner_units FOR SELECT USING (paliad.can_see_project(project_id)); + + +-- +-- Name: project_partner_units project_partner_units_write; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_partner_units_write ON paliad.project_partner_units USING (((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))) OR (EXISTS ( SELECT 1 + FROM paliad.project_teams pt + WHERE ((pt.user_id = auth.uid()) AND (pt.project_id = project_partner_units.project_id) AND (pt.responsibility = 'lead'::text)))))) WITH CHECK (((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))) OR (EXISTS ( SELECT 1 + FROM paliad.project_teams pt + WHERE ((pt.user_id = auth.uid()) AND (pt.project_id = project_partner_units.project_id) AND (pt.responsibility = 'lead'::text)))))); + + +-- +-- Name: project_teams; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.project_teams ENABLE ROW LEVEL SECURITY; + +-- +-- Name: project_teams project_teams_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_teams_delete ON paliad.project_teams FOR DELETE USING ((paliad.can_see_project(project_id) AND ((user_id = auth.uid()) OR (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))) OR paliad.effective_project_admin(auth.uid(), project_id)))); + + +-- +-- Name: project_teams project_teams_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_teams_insert ON paliad.project_teams FOR INSERT WITH CHECK (((user_id = auth.uid()) OR paliad.effective_project_admin(auth.uid(), project_id))); + + +-- +-- Name: project_teams project_teams_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_teams_select ON paliad.project_teams FOR SELECT TO authenticated USING (paliad.can_see_project(project_id)); + + +-- +-- Name: project_teams project_teams_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY project_teams_update ON paliad.project_teams FOR UPDATE USING (paliad.effective_project_admin(auth.uid(), project_id)) WITH CHECK (paliad.effective_project_admin(auth.uid(), project_id)); + + +-- +-- Name: projects; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.projects ENABLE ROW LEVEL SECURITY; + +-- +-- Name: projects projects_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY projects_delete ON paliad.projects FOR DELETE TO authenticated USING ((paliad.can_see_project(id) AND (EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text)))))); + + +-- +-- Name: projects projects_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY projects_insert ON paliad.projects FOR INSERT TO authenticated WITH CHECK (((parent_id IS NULL) OR paliad.can_see_project(parent_id))); + + +-- +-- Name: projects projects_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY projects_select ON paliad.projects FOR SELECT TO authenticated USING (paliad.can_see_project(id)); + + +-- +-- Name: projects projects_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY projects_update ON paliad.projects FOR UPDATE TO authenticated USING (paliad.can_see_project(id)) WITH CHECK (paliad.can_see_project(id)); + + +-- +-- Name: reminder_log; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.reminder_log ENABLE ROW LEVEL SECURITY; + +-- +-- Name: submission_drafts; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.submission_drafts ENABLE ROW LEVEL SECURITY; + +-- +-- Name: submission_drafts submission_drafts_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY submission_drafts_delete ON paliad.submission_drafts FOR DELETE TO authenticated USING (((user_id = auth.uid()) AND ((project_id IS NULL) OR paliad.can_see_project(project_id)))); + + +-- +-- Name: submission_drafts submission_drafts_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY submission_drafts_insert ON paliad.submission_drafts FOR INSERT TO authenticated WITH CHECK (((user_id = auth.uid()) AND ((project_id IS NULL) OR paliad.can_see_project(project_id)))); + + +-- +-- Name: submission_drafts submission_drafts_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY submission_drafts_select ON paliad.submission_drafts FOR SELECT TO authenticated USING ((((project_id IS NULL) AND (user_id = auth.uid())) OR ((project_id IS NOT NULL) AND paliad.can_see_project(project_id)))); + + +-- +-- Name: submission_drafts submission_drafts_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY submission_drafts_update ON paliad.submission_drafts FOR UPDATE TO authenticated USING (((user_id = auth.uid()) AND ((project_id IS NULL) OR paliad.can_see_project(project_id)))) WITH CHECK (((user_id = auth.uid()) AND ((project_id IS NULL) OR paliad.can_see_project(project_id)))); + + +-- +-- Name: system_audit_log; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.system_audit_log ENABLE ROW LEVEL SECURITY; + +-- +-- Name: system_audit_log system_audit_log_select_admin; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY system_audit_log_select_admin ON paliad.system_audit_log FOR SELECT USING ((EXISTS ( SELECT 1 + FROM paliad.users u + WHERE ((u.id = auth.uid()) AND (u.global_role = 'global_admin'::text))))); + + +-- +-- Name: system_audit_log system_audit_log_select_self; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY system_audit_log_select_self ON paliad.system_audit_log FOR SELECT USING ((actor_id = auth.uid())); + + +-- +-- Name: user_caldav_config; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_caldav_config ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_caldav_config user_caldav_self_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_caldav_self_delete ON paliad.user_caldav_config FOR DELETE TO authenticated USING ((user_id = auth.uid())); + + +-- +-- Name: user_caldav_config user_caldav_self_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_caldav_self_insert ON paliad.user_caldav_config FOR INSERT TO authenticated WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_caldav_config user_caldav_self_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_caldav_self_select ON paliad.user_caldav_config FOR SELECT TO authenticated USING ((user_id = auth.uid())); + + +-- +-- Name: user_caldav_config user_caldav_self_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_caldav_self_update ON paliad.user_caldav_config FOR UPDATE TO authenticated USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_calendar_bindings; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_calendar_bindings ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_calendar_bindings user_calendar_bindings_self_delete; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_calendar_bindings_self_delete ON paliad.user_calendar_bindings FOR DELETE TO authenticated USING ((user_id = auth.uid())); + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_self_insert; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_calendar_bindings_self_insert ON paliad.user_calendar_bindings FOR INSERT TO authenticated WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_self_select; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_calendar_bindings_self_select ON paliad.user_calendar_bindings FOR SELECT TO authenticated USING ((user_id = auth.uid())); + + +-- +-- Name: user_calendar_bindings user_calendar_bindings_self_update; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_calendar_bindings_self_update ON paliad.user_calendar_bindings FOR UPDATE TO authenticated USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_card_layouts; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_card_layouts ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_card_layouts user_card_layouts_owner_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_card_layouts_owner_all ON paliad.user_card_layouts USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_dashboard_layouts; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_dashboard_layouts ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_dashboard_layouts user_dashboard_layouts_owner_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_dashboard_layouts_owner_all ON paliad.user_dashboard_layouts USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_pinned_projects; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_pinned_projects ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_pinned_projects user_pinned_projects_owner_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_pinned_projects_owner_all ON paliad.user_pinned_projects USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: user_views; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.user_views ENABLE ROW LEVEL SECURITY; + +-- +-- Name: user_views user_views_owner_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY user_views_owner_all ON paliad.user_views USING ((user_id = auth.uid())) WITH CHECK ((user_id = auth.uid())); + + +-- +-- Name: users; Type: ROW SECURITY; Schema: paliad; Owner: - +-- + +ALTER TABLE paliad.users ENABLE ROW LEVEL SECURITY; + +-- +-- Name: users users_insert_self; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY users_insert_self ON paliad.users FOR INSERT TO authenticated WITH CHECK ((id = auth.uid())); + + +-- +-- Name: users users_select_all; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY users_select_all ON paliad.users FOR SELECT TO authenticated USING (true); + + +-- +-- Name: users users_update_self; Type: POLICY; Schema: paliad; Owner: - +-- + +CREATE POLICY users_update_self ON paliad.users FOR UPDATE TO authenticated USING ((id = auth.uid())) WITH CHECK ((id = auth.uid())); + + +-- +-- PostgreSQL database dump complete +-- + + +-- +-- PostgreSQL database dump +-- + + +-- Dumped from database version 15.8 +-- Dumped by pg_dump version 16.11 (Debian 16.11-1.pgdg13+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: applied_migrations; Type: TABLE DATA; Schema: paliad; Owner: - +-- + +COPY paliad.applied_migrations (version, name, applied_at, checksum) FROM stdin; +1 paliad_schema 2026-05-20 11:00:25.874869+00 \N +2 users 2026-05-20 11:00:25.917868+00 \N +3 reference_tables 2026-05-20 11:00:25.919459+00 \N +4 akten 2026-05-20 11:00:25.927919+00 \N +5 akten_children 2026-05-20 11:00:25.930627+00 \N +6 visibility 2026-05-20 11:00:25.931624+00 \N +7 rls_policies 2026-05-20 11:00:25.932543+00 \N +8 seed_proceeding_types 2026-05-20 11:00:25.935923+00 \N +9 seed_deadline_rules 2026-05-20 11:00:25.936882+00 \N +10 seed_holidays 2026-05-20 11:00:25.938217+00 \N +11 feedback_tables 2026-05-20 11:00:25.939092+00 \N +12 fristenrechner_rules 2026-05-20 11:00:25.942431+00 \N +13 user_caldav_config 2026-05-20 11:00:25.943934+00 \N +14 checklist_instances 2026-05-20 11:00:25.945502+00 \N +15 user_role_open_and_dezernat 2026-05-20 11:00:25.946233+00 \N +16 email_tables 2026-05-20 11:00:25.950026+00 \N +17 user_preferences 2026-05-20 11:00:25.950832+00 \N +18 projects_v2 2026-05-20 11:00:25.951506+00 \N +19 seed_departments_from_user_text 2026-05-20 11:00:25.952095+00 \N +20 rename_tables_english 2026-05-20 11:00:25.95302+00 \N +21 fix_function_bodies_after_rename 2026-05-20 11:00:25.953899+00 \N +22 user_reminder_times 2026-05-20 11:00:25.95751+00 \N +23 split_job_title_and_global_role 2026-05-20 11:00:25.958348+00 \N +24 rename_department_columns 2026-05-20 11:00:25.959083+00 \N +25 reminder_redesign 2026-05-20 11:00:25.960216+00 \N +26 email_templates 2026-05-20 11:00:25.963295+00 \N +27 rename_to_partner_units 2026-05-20 11:00:25.963942+00 \N +28 youpc_deadlines_import 2026-05-20 11:00:25.964586+00 \N +29 tier1_rule_fixes 2026-05-20 11:00:25.96552+00 \N +30 event_types 2026-05-20 11:00:25.967726+00 \N +31 tier2_fristenrechner_ports 2026-05-20 11:00:25.970953+00 \N +32 deadline_notes_en 2026-05-20 11:00:25.973916+00 \N +33 trigger_events_de 2026-05-20 11:00:25.974845+00 \N +34 deadlines_rule_code 2026-05-20 11:00:25.97575+00 \N +35 event_deadlines_title_de_backfill 2026-05-20 11:00:25.976548+00 \N +36 event_deadlines_notes_en 2026-05-20 11:00:25.980943+00 \N +37 deadline_concepts 2026-05-20 11:00:25.981803+00 \N +38 concept_links_and_legal_source 2026-05-20 11:00:25.98254+00 \N +39 condition_flag_to_array 2026-05-20 11:00:25.984325+00 \N +40 seed_concepts_and_backfill 2026-05-20 11:00:25.987065+00 \N +41 upc_counterclaim_cross_flows 2026-05-20 11:00:25.98876+00 \N +42 de_expansion_b3 2026-05-20 11:00:25.989574+00 \N +43 de_instance_split_proceedings 2026-05-20 11:00:25.990331+00 \N +44 dpma_proceedings 2026-05-20 11:00:25.991311+00 \N +45 epa_gap_fill 2026-05-20 11:00:25.99205+00 \N +46 cross_cutting_triggers 2026-05-20 11:00:25.994656+00 \N +47 deadline_search_view 2026-05-20 11:00:26.003625+00 \N +48 event_categories 2026-05-20 11:00:26.024354+00 \N +49 event_categories_seed 2026-05-20 11:00:26.02962+00 \N +50 bilateral_rules_backfill 2026-05-20 11:00:26.030816+00 \N +51 proceeding_display_order 2026-05-20 11:00:26.031857+00 \N +52 event_categories_rop_audit 2026-05-20 11:00:26.032718+00 \N +53 courts_and_countries 2026-05-20 11:00:26.033651+00 \N +54 approvals 2026-05-20 11:00:26.035628+00 \N +55 hierarchy_aggregation 2026-05-20 11:00:26.03672+00 \N +56 user_views 2026-05-20 11:00:26.037805+00 \N +57 email_broadcasts 2026-05-20 11:00:26.038468+00 \N +58 paliadin_poc 2026-05-20 11:00:26.039059+00 \N +59 profession_vs_responsibility 2026-05-20 11:00:26.039691+00 \N +60 user_pinned_projects 2026-05-20 11:00:26.040427+00 \N +61 user_card_layouts 2026-05-20 11:00:26.041012+00 \N +62 approval_policy_unit_defaults 2026-05-20 11:00:26.043555+00 \N +63 frist_verpasst_upc 2026-05-20 11:00:26.044522+00 \N +64 users_forum_pref 2026-05-20 11:00:26.045482+00 \N +65 event_categories_forums 2026-05-20 11:00:26.046339+00 \N +66 approval_policy_split 2026-05-20 11:00:26.047168+00 \N +67 approval_policy_drop_required_role 2026-05-20 11:00:26.047878+00 \N +68 deadline_rules_is_optional 2026-05-20 11:00:26.04967+00 \N +69 event_categories_opponent_more_types 2026-05-20 11:00:26.05093+00 \N +70 paliadin_inline 2026-05-20 11:00:26.052469+00 \N +71 event_categories_party 2026-05-20 11:00:26.053417+00 \N +72 projects_our_side 2026-05-20 11:00:26.055008+00 \N +73 deadline_concept_event_types 2026-05-20 11:00:26.056124+00 \N +74 deadline_concept_event_types_jurisdiction 2026-05-20 11:00:26.05762+00 \N +75 project_events_timeline_kind 2026-05-20 11:00:26.059218+00 \N +76 smart_timeline_slice_2 2026-05-20 11:00:26.060448+00 \N +77 projects_counterclaim_of 2026-05-20 11:00:26.063145+00 \N +78 unified_rule_columns 2026-05-20 11:00:26.064504+00 \N +79 deadline_rule_audit 2026-05-20 11:00:26.066326+00 \N +80 projects_instance_level 2026-05-20 11:00:26.06735+00 \N +82 backfill_is_court_set 2026-05-20 11:00:26.068412+00 \N +83 backfill_priority 2026-05-20 11:00:26.069138+00 \N +84 backfill_condition_expr 2026-05-20 11:00:26.072471+00 \N +85 pipeline_c_data_move 2026-05-20 11:00:26.073554+00 \N +86 event_deadlines_readonly 2026-05-20 11:00:26.076826+00 \N +87 project_proceeding_type_remap 2026-05-20 11:00:26.078807+00 \N +88 project_proceeding_type_check 2026-05-20 11:00:26.079539+00 \N +89 deadline_rule_backfill_orphans 2026-05-20 11:00:26.080377+00 \N +90 backfill_deadline_rule_id 2026-05-20 11:00:26.118661+00 \N +91 drop_legacy_rule_columns 2026-05-20 11:00:26.120138+00 \N +92 drop_event_deadlines_tables 2026-05-20 11:00:26.123024+00 \N +93 retire_litigation_category 2026-05-20 11:00:26.123779+00 \N +94 clientmatter_six_digit 2026-05-20 11:00:26.124569+00 \N +95 fristen_gap_fill 2026-05-20 11:00:26.125564+00 \N +96 proceeding_code_rename 2026-05-20 11:00:26.126743+00 \N +97 legal_citation_backfill 2026-05-20 11:00:26.129311+00 \N +98 submission_codes_prefix_and_rename 2026-05-20 11:00:26.133163+00 \N +99 drop_with_po_flag 2026-05-20 11:00:26.137242+00 \N +100 ccr_visible_rule 2026-05-20 11:00:26.138524+00 \N +101 caldav_multi_calendar 2026-05-20 11:00:26.139322+00 \N +102 system_audit_log 2026-05-20 11:00:26.140697+00 \N +103 approval_suggest_changes 2026-05-20 11:00:26.141448+00 \N +104 einspruch_name_and_ccr_priority 2026-05-20 11:00:26.144366+00 \N +105 upc_inf_track_aware_sequence 2026-05-20 11:00:26.145459+00 \N +106 add_madrid_office 2026-05-20 11:00:26.146001+00 \N +107 caldav_sync_log_binding_id 2026-05-20 11:06:27.894942+00 349649aa6c7b5115622e136972beb3f6207490ba942297cb76d4c1a477ab7b86 +108 caldav_mkcalendar_capability 2026-05-20 11:27:28.578558+00 cac45d51ceb60e813d10e1075761b2b23d0f2c7571e2bb92730230c2913a3fad +109 user_dashboard_layouts 2026-05-20 11:56:59.717532+00 abb4872a70918bd7c51b635a631843f8aac940ffaf86e16f4026b6c4ee6ee1e5 +110 project_type_other 2026-05-20 12:44:46.655807+00 658d3bd3db884298bb47b1223b64a1cb1e3cc30e3e5e8fbff25a59b93eaeda11 +111 project_admin_and_select 2026-05-20 12:47:56.455109+00 40f72318e4690cebd1e4a0c821d29cbeee79e0d33de2b6201ee81ecb8909fd0f +112 client_role_rework 2026-05-20 12:57:10.272552+00 759074e8c21013d27c21c1b58331f913ef110d6444182c050e3f3fac7c111092 +113 projects_opponent_code 2026-05-20 12:57:11.158022+00 8a0475b282dd4d327d4cf7d8f41fe9f57a7ff0f6bbc0e95a12e61c5e3b6829d8 +114 user_checklists 2026-05-20 13:25:18.831769+00 9bbb043c86c1be22fb30e8bf629beab7edcc8ab5e1bb606b57b05ff5e7904bf2 +115 checklist_shares 2026-05-20 13:40:44.175474+00 d902111dc7e2bc6ad73f14721b622b218395eb0007409b75731b5ffbc09d3c9b +116 checklist_versioning 2026-05-20 13:52:27.236811+00 f4ddf44f4b3c688f5c2a19412943c714c71eb28ce25652fbd35380e4f19edf91 +117 firm_dashboard_default 2026-05-20 17:31:33.441923+00 3c68fa6a3d8df8e2cae0b02e066c2052b275148df7dc26995695d7e94ab687c7 +118 paliadin_aichat_conversation 2026-05-22 13:23:58.929489+00 44413ea1823a1064ac1703784ad445862545deaf12aa2454e07b69d755b9c3f3 +119 submission_drafts 2026-05-22 22:21:06.284791+00 e7061bbcf2ed17e30796e0b7a5468fc69d3304b2b1173ede50a6cd6c22b72642 +120 submission_drafts_project_optional 2026-05-23 00:21:51.710348+00 c88bca6352e967c26f31925c33f0c12dbf626dc3aa1494131d5397fa42ab8401 +121 proceeding_trigger_event_label 2026-05-25 12:01:35.367194+00 34547efe3120f650aacdc4c7565ada03b875ec6a3939054cd3910a2160fe6fa7 +122 deadlines_custom_rule_text 2026-05-25 12:58:21.816949+00 a7cc4f1f25ec35d53d157cd00a8f55fcb56e1ff6ade25af62a1636182fc11115 +123 backups 2026-05-25 13:44:12.941055+00 a96b7dd9c3bd8775bfc23d87f60268d91bb34171a61945b92d495269050d052c +124 de_inf_lg_replik_duplik_sequencing 2026-05-25 13:49:03.90125+00 9318043b1bee99a148cbb742373f87dbc8c6115e691f56b6dc37954e77644a25 +126 users_inbox_seen_at 2026-05-25 13:51:46.706075+00 ba820aa869b574eacf6eced7c1634b2d58b002f5efedff231ae76cc448f040d4 +125 cross_cutting_filter_legal_source 2026-05-25 14:13:21.29658+00 38b337e2cf0b70d8e69f628c37e3ea3f12c170b250a469f99d30b1ff628dd2cf +127 wave0_tier0_deadline_fixes 2026-05-25 14:13:21.365458+00 de19ceb1bf1de251a5f8cb25881ea140a9f91299c9197509328e296c98b704c1 +128 deadline_rules_unit_check 2026-05-25 14:13:21.372977+00 0db376f71a7da07735702fc5c89ac6e3d3378871816bd73a2009e29c66624872 +129 project_event_choices 2026-05-25 15:02:26.177418+00 888a41e7983cdad2dbd5e029e4d1c73777eb31fdf7125e034bbf8926d9a82605 +131 submission_drafts_party_selection 2026-05-25 15:02:26.187667+00 fd3e56307cb85292aa0aee5b3ede2ef134e3f2504feb2f1cd096a380beba4053 +130 submission_drafts_language 2026-05-25 15:05:46.905009+00 2596da220f2e7ca5b20b11eaf5d6d29d92f9acbc991bf9f604038a7fb012d352 +\. + + +-- +-- PostgreSQL database dump complete +-- + + diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 2170f1c..f7ad569 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -1,9 +1,13 @@ package handlers import ( + "context" "encoding/json" "net/http" "strings" + "time" + + "github.com/jmoiron/sqlx" "mgit.msbls.de/m/paliad/internal/auth" "mgit.msbls.de/m/paliad/internal/services" @@ -50,6 +54,12 @@ func noCachePages(h http.Handler) http.Handler { // Services bundles the Phase B + C database-backed services. Pass nil if // DATABASE_URL was unset; the matter-management endpoints will return 503. type Services struct { + // Pool is the raw connection pool. Held so the readiness probe + // (/health/ready) can ping it without going through any individual + // service. nil when DATABASE_URL was unset — in that case + // /health/ready returns 503. + Pool *sqlx.DB + Project *services.ProjectService Team *services.TeamService PartnerUnit *services.PartnerUnitService @@ -188,6 +198,38 @@ func Register(mux *http.ServeMux, client *auth.Client, giteaAPIToken string, svc _, _ = w.Write([]byte("ok\n")) }) + // Readiness probe. Public, no auth. Distinct from /healthz: this + // returns 200 only when the DB pool is reachable. Reaching Register + // at all implies db.ApplyMigrations succeeded (cmd/server/main.go + // calls it before constructing svc), so a 200 here means "migrations + // applied AND pool responsive" — the contract Dokploy / Traefik should + // gate on, not the bind-and-serve check that /healthz answers. + // + // Three outcomes: + // - svc == nil OR svc.Pool == nil → 503 (DB-less knowledge-platform + // deployments report not-ready so an external orchestrator can + // distinguish them from a full prod boot). + // - PingContext fails within 2 s → 503 (pool unreachable). + // - PingContext succeeds → 200 "ready". + // + // Used by docker-compose.yml's healthcheck (Slice B) and by the + // post-deploy verification step in .gitea/workflows/test.yaml. + mux.HandleFunc("GET /health/ready", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + if svc == nil || svc.Pool == nil { + http.Error(w, "db not configured\n", http.StatusServiceUnavailable) + return + } + ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) + defer cancel() + if err := svc.Pool.PingContext(ctx); err != nil { + http.Error(w, "db unreachable\n", http.StatusServiceUnavailable) + return + } + _, _ = w.Write([]byte("ready\n")) + }) + // API endpoints (JSON, public) mux.HandleFunc("POST /api/login", handleAPILogin) mux.HandleFunc("POST /api/register", handleAPIRegister)