paliad's RemotePaliadinService shells out to `ssh m@mriver paliadin-shim`
to deliver Paliadin turns from prod (paliad.de Dokploy container) to
mRiver where the long-lived tmux+claude pane lives. The alpine final
stage didn't ship an SSH client; add openssh-client (~1.1MB compressed).
The Go service wires this up in a follow-up commit (Paliadin interface
split). When PALIADIN_REMOTE_HOST is unset, the binary still picks up
the local-tmux PoC path and never invokes ssh, so this change is safe
on its own.
Refs m/paliad#12
Implements docs/design-kanzlai-integration.md §8 Phase A.
Schema (paliad.*):
- users (extends auth.users) with office, practice_group, role
- akten with visibility columns: owning_office, collaborators uuid[],
firm_wide_visible (per design §2)
- parteien, fristen, termine, dokumente, akten_events, notizen
(polymorphic notes; notizen_exactly_one_parent CHECK)
- proceeding_types, deadline_rules, holidays (reference data)
- 4 feedback tables re-namespaced from public.* into paliad.*
(handler swap to direct DB is a follow-up; old public tables stay
intact for now and continue serving via PostgREST)
Visibility (paliad.can_see_akte):
- single SQL function, used by every RLS policy
- predicate: firm_wide_visible OR owning_office matches user's office
OR auth.uid() ∈ collaborators OR user is admin
- mirrored at app layer in Phase B (defense in depth)
RLS (real, not permissive):
- akten: visibility predicate; insert restricted to own office or admin;
delete restricted to partners + admins
- parteien/fristen/dokumente/akten_events: inherit via can_see_akte(akte_id)
- termine: personal (akte_id NULL) visible only to creator; Akte-linked
follow visibility predicate
- notizen: paliad.notiz_is_visible() resolves polymorphic parent
- reference tables: SELECT for any authenticated user
- users: SELECT all; UPDATE/INSERT only self
- feedback tables: INSERT for any authenticated user (write-only)
Seed data (ported from KanzlAI seed_upc_timeline.sql):
- 7 proceeding_types (INF, REV, CCR, APM, APP, AMD, ZPO_CIVIL)
- 40 deadline_rules (32 UPC + 4 ZPO + 4 cross-type appeal spawns)
including conditional logic: Reply rule code (RoP.029b → 029a) and
Rejoinder duration (1mo → 2mo) flip when CCR active
- 55 holidays (DE federal 2026/2027 + UPC summer 2026 + UPC winter 26/27)
Indexes per audit §3.3 + visibility-predicate hot paths:
- akten: (status, owning_office), (owning_office), partial on
firm_wide_visible, GIN on collaborators
- fristen: (status, due_date), (akte_id)
- termine: (start_at), (akte_id)
- akten_events: (akte_id, created_at DESC)
- notizen: 4 partial indexes per parent type
- users: (office), (role)
Migration tooling:
- golang-migrate/migrate/v4 with embed.FS source
- Migrations live in internal/db/migrations/ (Go embed can't reach
outside the package; this is the conventional Go layout for embedded
migrations)
- Applied at server startup before HTTP listener binds
- DATABASE_URL is optional today (existing knowledge tools work without
DB); becomes required once Phase B services land
- Mock Supabase auth schema for local testing in
internal/db/migrations/_dev/mock_supabase_auth.sql (excluded from
embed pattern by the underscore prefix)
Other changes:
- Dockerfile: bump golang to 1.24, copy go.sum (audit §2.9), rename
binary patholo → paliad
- docker-compose.yml: add DATABASE_URL passthrough
- README.md: rewritten to reflect Paliad brand + Phase A migration system
Verified locally:
- 11 migrations applied cleanly against postgres:16-alpine
- RLS enabled on all 15 paliad.* tables (verified via pg_class.relrowsecurity)
- Visibility predicate verified with 4-case scenario:
- Alice (Munich associate): sees Munich + firm-wide + collab-on (t f t t)
- Bob (Düsseldorf associate): sees Düsseldorf + firm-wide + collab-on (f t t t)
- Carol (Munich partner): sees Munich + firm-wide only (t f t f)
- Anonymous: sees firm-wide only (f f t f)
- migrate down + re-up cycle clean (initial 007 down had ordering bug,
fixed: drop policies before referenced function)
- Existing endpoints (/, /login) return 302 + 200 — no regressions
Replace Go HTML template rendering with a Bun + TSX build-time static
site generator. Go backend becomes API-only for auth.
Frontend:
- Custom JSX-to-HTML-string factory (zero dependencies)
- TSX components for Header, Footer, index page, login page
- Client-side login.ts handles tab switching and fetch()-based auth
- Bun bundler compiles client JS, build.ts renders pages to dist/
Backend:
- Auth handlers return JSON (POST /api/login, POST /api/register)
- Login page served as static HTML from dist/
- Static assets served from /assets/ (public)
- Auth middleware unchanged (cookie check, redirect to /login)
- Removed template parsing and renderPage
Dockerfile:
- 3-stage build: Bun frontend -> Go backend -> alpine runtime
- Frontend dist copied to /app/dist in final image
Removed: templates/, static/css/ (replaced by frontend/)
Go web server (net/http, port 8080) serving HTML templates with a
professional landing page for patholo.de. Multi-stage Dockerfile
and docker-compose.yml ready for Dokploy deployment.
- cmd/server/main.go: HTTP server entry point
- internal/handlers: route registration and template rendering
- templates/: base layout + bilingual landing page (DE/EN)
- static/css/: clean, responsive CSS with HL navy branding
- Dockerfile: multi-stage build (golang:1.23-alpine -> alpine:3.21)
- docker-compose.yml: single web service on port 8080