F-6 from t-paliad-074 architecture audit. The Gitea repo was renamed m/patholo → mAi/paliad → m/paliad, but go.mod still declared `mgit.msbls.de/m/patholo` and every internal import echoed the pre-rebrand name. Sweep: - go.mod: module path → mgit.msbls.de/m/paliad - All *.go files: imports rewritten via sed - README.md, docs/design-kanzlai-integration.md: mAi/paliad → m/paliad - Frontend issue-reference comments (mAi/paliad#N → m/paliad#N) in i18n.ts, theme.ts, sidebar.ts, app.ts, Sidebar.tsx, PWAHead.tsx, global.css Verified: go build/vet/test ./... clean, bun run build clean, no remaining mgit.msbls.de/m/patholo or mAi/paliad references outside docs that intentionally describe the rename history.
777 lines
46 KiB
Markdown
777 lines
46 KiB
Markdown
# KanzlAI → Paliad Integration
|
||
|
||
**Author:** cronus (inventor)
|
||
**Date:** 2026-04-16
|
||
**Task:** t-paliad-001
|
||
**Revision:** 2 (incorporates m's feedback via athena 2026-04-16: office-scoped visibility from day one; Mandate → Akten; Outlook as long-term risk; compliance handled out-of-band)
|
||
**Status:** Design draft for review
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
**Recommendation: Full merge into a single Paliad codebase, single Go binary, single Supabase schema, single Dokploy deployment.** Port KanzlAI's domain logic (deadline rules, calculator, holiday service, Akten/Fristen/Termine/notes models, CalDAV sync, AI extraction) into Paliad's existing Go backend. Rewrite KanzlAI's React+Next.js frontend in Paliad's Bun + TSX stack. Retire `kanzlai.msbls.de` to a 301 redirect once parity is reached.
|
||
|
||
**Why this path.** Two backends behind one domain doubles operational surface for zero user benefit. The two products share an audience (HLC patent practice), a stack philosophy (Go + Supabase), and an auth model (Supabase password). The only real cost is rewriting the React frontend — which is a smaller burden than it sounds, because KanzlAI's UI is mostly forms, lists, calendar grids, and tabbed details, all of which fit Paliad's server-rendered TSX + per-page client JS pattern. The merger from HL → HLC is the right moment to consolidate: one platform, one URL, one mental model. Knowledge tools and Aktenverwaltung belong together — a lawyer who looks up a UPC fee on Paliad should also see their next UPC deadline.
|
||
|
||
---
|
||
|
||
## 1. Integration strategy
|
||
|
||
### Decision: Full merge into Paliad's stack
|
||
|
||
Four options were considered:
|
||
|
||
| Option | Backend | Frontend | UI | Verdict |
|
||
|---|---|---|---|---|
|
||
| **A. Run both apps behind unified domain** | 2 binaries (Go + Go) | 2 stacks (Bun TSX + Next) | 2 sidebars, reverse-proxy split | Doubles ops; jarring UX seam between `/` and `/app/*` |
|
||
| **B. Port frontend only, keep backends separate** | 2 binaries | 1 stack (Bun TSX) | 1 sidebar, 2 API clients | Eliminates UI seam but keeps deploy/schema/auth split |
|
||
| **C. Full merge — port KanzlAI into Paliad** | 1 binary | 1 stack (Bun TSX) | 1 sidebar | Higher upfront cost, lowest ongoing cost ✅ |
|
||
| **D. Incremental — start with deadlines, then add others** | Phased | Phased | Phased | This is the *implementation* of C, not an alternative |
|
||
|
||
**Pick C, execute as D.** Option C is the destination; the phased migration plan in §8 *is* the incremental path.
|
||
|
||
### Why not keep KanzlAI as-is and link to it?
|
||
|
||
- KanzlAI has unfixed critical security issues (`AUDIT.md` §1.1 tenant isolation bypass, §1.3 plaintext CalDAV creds, §1.4 no CORS, §1.6 race condition in HolidayService). Shipping it under the HLC name without fixing those is malpractice. Fixing them requires a full pass anyway — better to do the fixes inside Paliad than touch a deprecated stack.
|
||
- React + Next.js + Tailwind v4 + react-query is too much machinery for Paliad's profile (small content + tools site). Maintaining two frontends is a tax we don't want.
|
||
- KanzlAI has zero production users today (per audit). Now is the cheapest possible moment to consolidate.
|
||
|
||
### Trade-off accepted
|
||
|
||
- **~6 weeks of focused implementation** (estimated 56h coder time, see §8) to retire ~16,500 lines of KanzlAI code and rebuild ~4,500 lines on Paliad's stack.
|
||
- **Lose React ecosystem** (react-query, react-day-picker, etc.). Paliad's stack handles these patterns with HTML-first forms + small per-page TS bundles. Acceptable.
|
||
|
||
---
|
||
|
||
## 2. Auth & visibility
|
||
|
||
### Decision: Office-scoped visibility from day one. Drop multi-tenancy infrastructure.
|
||
|
||
KanzlAI was built multi-tenant in case it became a SaaS product. That bet is dead — Paliad serves one firm. But "one firm" doesn't mean "every lawyer sees every Akte". Patent practice at HLC is **office-anchored**: a Munich associate works on Munich Akten; a partner may have visibility across offices; cross-office collaboration happens via explicit naming (collaborator lists), not via blanket access.
|
||
|
||
**Strip the SaaS multi-tenancy plumbing (`tenants`, `user_tenants`, X-Tenant-ID headers, the bypass bug). Build office-scoped visibility natively in its place.**
|
||
|
||
### Concrete model
|
||
|
||
#### `paliad.users` table (extends `auth.users`)
|
||
|
||
```
|
||
paliad.users
|
||
id uuid PK / FK auth.users.id
|
||
email text (synced from auth.users; for display)
|
||
display_name text
|
||
office text NOT NULL CHECK (office IN
|
||
('munich','duesseldorf','hamburg',
|
||
'amsterdam','london','paris','milan'))
|
||
practice_group text NULL -- e.g. 'patents-litigation', 'patents-prosecution'
|
||
role text NOT NULL DEFAULT 'associate' CHECK (role IN
|
||
('partner','associate','pa','admin'))
|
||
created_at timestamptz DEFAULT now()
|
||
```
|
||
|
||
`office` is mandatory; new users pick during first-login onboarding. `practice_group` is optional (used for filtering, not access control today).
|
||
|
||
#### Visibility on `paliad.akten` (and child rows)
|
||
|
||
Every Akte carries explicit visibility:
|
||
|
||
```
|
||
paliad.akten
|
||
id uuid PK
|
||
aktenzeichen text NOT NULL -- internal reference
|
||
title text NOT NULL
|
||
status text ...
|
||
owning_office text NOT NULL -- the office the Akte "belongs to"
|
||
collaborators uuid[] DEFAULT '{}' -- explicit user IDs with access
|
||
firm_wide_visible boolean NOT NULL DEFAULT false -- partner-toggled override
|
||
created_by uuid FK paliad.users(id)
|
||
created_at, updated_at
|
||
...
|
||
```
|
||
|
||
Visibility rule (the canonical predicate, used by RLS *and* by query helpers):
|
||
|
||
```
|
||
A user U can see Akte A iff:
|
||
A.firm_wide_visible = true
|
||
OR A.owning_office = U.office
|
||
OR U.id = ANY(A.collaborators)
|
||
OR U.role = 'admin'
|
||
```
|
||
|
||
#### RLS policies (enforced from day one)
|
||
|
||
A SQL function, then policies that use it:
|
||
|
||
```sql
|
||
CREATE OR REPLACE FUNCTION paliad.can_see_akte(akte_id uuid)
|
||
RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $$
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM paliad.akten a
|
||
JOIN paliad.users u ON u.id = auth.uid()
|
||
WHERE a.id = akte_id
|
||
AND (
|
||
a.firm_wide_visible
|
||
OR a.owning_office = u.office
|
||
OR auth.uid() = ANY(a.collaborators)
|
||
OR u.role = 'admin'
|
||
)
|
||
);
|
||
$$;
|
||
|
||
-- akten themselves
|
||
CREATE POLICY akten_select ON paliad.akten FOR SELECT
|
||
USING (paliad.can_see_akte(id));
|
||
CREATE POLICY akten_insert ON paliad.akten FOR INSERT
|
||
WITH CHECK (
|
||
-- user can only create Akten in their own office
|
||
-- (admins can create anywhere)
|
||
owning_office = (SELECT office FROM paliad.users WHERE id = auth.uid())
|
||
OR (SELECT role FROM paliad.users WHERE id = auth.uid()) = 'admin'
|
||
);
|
||
CREATE POLICY akten_update ON paliad.akten FOR UPDATE
|
||
USING (paliad.can_see_akte(id))
|
||
WITH CHECK (paliad.can_see_akte(id));
|
||
CREATE POLICY akten_delete ON paliad.akten FOR DELETE
|
||
USING (
|
||
paliad.can_see_akte(id)
|
||
AND (SELECT role FROM paliad.users WHERE id = auth.uid()) IN ('partner','admin')
|
||
);
|
||
|
||
-- child rows (parteien, fristen, termine, dokumente, akten_events, notizen)
|
||
-- inherit visibility via akte_id
|
||
CREATE POLICY fristen_select ON paliad.fristen FOR SELECT
|
||
USING (paliad.can_see_akte(akte_id));
|
||
-- ...analogous for the rest
|
||
```
|
||
|
||
Notes:
|
||
- `firm_wide_visible` is the partner override for cross-office matters of firm-wide interest (e.g., a strategic UPC test case).
|
||
- `collaborators` is the per-Akte explicit access list — how cross-office teams collaborate without flipping the firm-wide flag.
|
||
- Only partners and admins can delete Akten; everyone with visibility can update.
|
||
- Reference data (`proceeding_types`, `deadline_rules`, `holidays`) is global — RLS on but with `USING (true)` permissive policies. These tables hold no user data.
|
||
- Knowledge content tables (`link_suggestions`, `checklisten_feedback`, `gerichte_feedback`) stay as-is — feedback is intentionally firm-wide.
|
||
|
||
#### Auth gate (registration)
|
||
|
||
Email domain whitelist, env-configured: `[@hoganlovells.com, @hlc.com, @hlc.de]`. Easy to extend if HLC picks a new TLD.
|
||
|
||
#### Onboarding flow (new in Phase D)
|
||
|
||
After first sign-in: prompt for office (required) + practice group (optional) + role (defaults to `associate`; partners self-identify, admins set by SQL initially). Without this row in `paliad.users`, the user can't see any Akten — that's the safe default.
|
||
|
||
### Backend implications
|
||
|
||
- All Akten queries route through a `service.ListVisibleForUser(ctx, userID)` helper that enforces the predicate at the application layer too (defense in depth — RLS is the floor, not the ceiling).
|
||
- "All Akten in the firm" view exists for admins/partners as a feature, not as the default.
|
||
- The Dashboard (Phase G) shows *visible* Akten only.
|
||
|
||
### Post-merger email transition
|
||
|
||
- Phase 1 (now → merger): whitelist `[@hoganlovells.com, @hlc.com, @hlc.de]`. Any new HLC subdomain TLD added to the env config slice.
|
||
- Phase 2 (post-merger): existing `@hoganlovells.com` Supabase identities continue to work (Supabase keys on UUID, not email). New users register under `@hlc.*`. No data migration needed.
|
||
- Phase 3 (cleanup, +1 year): optional script to update display emails in `paliad.users` if HLC actually retires the old domain.
|
||
|
||
### When the model needs to extend
|
||
|
||
- **Practice-group scoping** (e.g., "Patents Litigation can't see Patents Prosecution Akten"): add `visible_to_groups text[]` column on `paliad.akten`, extend the RLS predicate. Don't build now — wait until a partner asks.
|
||
- **External counsel access** (e.g., bringing in an Italian boutique on a Milan Akte): add a separate `external_collaborators` table with email + scoped RLS. Don't build now.
|
||
- **Read-only Akten archive after closure**: add `is_archived` flag, deny mutations via RLS. Cheap follow-on.
|
||
|
||
---
|
||
|
||
## 3. Database / schema
|
||
|
||
### Decision: Single `paliad` schema in the youpc Supabase instance. Migrate `kanzlai` tables in.
|
||
|
||
KanzlAI's schema lives in `kanzlai.*` on the youpc Supabase instance. Paliad currently has 3 feedback tables (`link_suggestions`, `checklisten_feedback`, `gerichte_feedback`) that look like they're in the public schema (no schema prefix in `docs/migrations/00*.sql`).
|
||
|
||
**Plan:**
|
||
1. Create `paliad` schema in the same youpc Supabase instance (no new project).
|
||
2. Move Paliad's 3 existing feedback tables into `paliad.*` (CREATE in new schema, COPY data, drop old).
|
||
3. Port KanzlAI's tables into `paliad.*`, dropping multi-tenancy fields and adding visibility columns:
|
||
- `cases` → `paliad.akten` (German throughout: table name, Go struct `Akte`, URL `/akten`, UI label "Akten")
|
||
- `parties` → `paliad.parteien`
|
||
- `deadlines` → `paliad.fristen`
|
||
- `appointments` → `paliad.termine`
|
||
- `documents` → `paliad.dokumente`
|
||
- `case_events` → `paliad.akten_events`
|
||
- `notes` → `paliad.notizen`
|
||
- All drop `tenant_id`, all add `akte_id` FK to `paliad.akten`, all gain visibility via `paliad.can_see_akte(akte_id)` in RLS.
|
||
- `paliad.akten` itself adds `owning_office`, `collaborators uuid[]`, `firm_wide_visible boolean` per §2.
|
||
- Reference tables: `proceeding_types`, `deadline_rules`, `holidays` → `paliad.*`. RLS-on but permissive (no user data).
|
||
- New: `paliad.users` per §2.
|
||
4. RLS enabled on all paliad tables with **real visibility policies** per §2 (not just permissive-authenticated).
|
||
5. Retire `kanzlai.*` schema once cutover is verified (`DROP SCHEMA kanzlai CASCADE` in Phase J).
|
||
|
||
### German vs. English naming
|
||
|
||
Going **fully German** end-to-end (table, struct, URL, UI). Reasons:
|
||
- Paliad already uses German nouns for routes (`/fristen`, `/termine`, `/glossar`, `/checklisten`). Mixing German routes with English internals creates lifelong cognitive translation overhead — KanzlAI showed this with `cases` (English) ↔ "Akten" (German UI).
|
||
- The audience reads German legal terminology natively.
|
||
- Go struct names allow Unicode-clean ASCII (`Akte`, `Frist`, `Termin`) — no friction.
|
||
- Database identifiers in lowercase German plurals (`akten`, `fristen`, `parteien`) read fine to anyone who's worked with German data.
|
||
|
||
Concrete naming convention:
|
||
| Concept | DB table | Go struct | Go service | URL | German UI |
|
||
|---|---|---|---|---|---|
|
||
| Matter | `paliad.akten` | `Akte` | `AkteService` | `/akten` | "Akte" / "Akten" |
|
||
| Party | `paliad.parteien` | `Partei` | `ParteienService` | (sub of Akte) | "Partei" / "Parteien" |
|
||
| Deadline | `paliad.fristen` | `Frist` | `FristService` | `/fristen` | "Frist" / "Fristen" |
|
||
| Appointment | `paliad.termine` | `Termin` | `TerminService` | `/termine` | "Termin" / "Termine" |
|
||
| Document | `paliad.dokumente` | `Dokument` | `DokumentService` | (sub of Akte) | "Dokument" |
|
||
| Audit event | `paliad.akten_events` | `AkteEvent` | (in `AkteService`) | n/a | "Verlauf" |
|
||
| Note | `paliad.notizen` | `Notiz` | `NotizenService` | (cross-cutting) | "Notiz" / "Notizen" |
|
||
| User | `paliad.users` | `User` | `UserService` | n/a | n/a |
|
||
|
||
`User` stays English (it's a Supabase concept, not a legal one).
|
||
|
||
### Reuse vs. port
|
||
|
||
- **Reuse the data**: KanzlAI's `deadline_rules` table has 32 UPC + 4 ZPO rules (more than Paliad's hardcoded `internal/calc/deadline_rules.go`). Port the data, drop Paliad's hardcoded rules.
|
||
- **Reuse the holiday data**: KanzlAI's `holidays` table has DE federal + UPC judicial vacations 2026. Port and extend.
|
||
- **Don't reuse**: KanzlAI's `tenants`, `user_tenants`, `billing_rates`, `invoices`, `time_entries` (the latter three are aspirational — handler stubs exist but no real implementation). Drop entirely.
|
||
|
||
### Migration tooling
|
||
|
||
Paliad currently has SQL files in `docs/migrations/` with no runner. Pick a tool now while the schema is small:
|
||
|
||
- **Choice: `golang-migrate/migrate`** as a sidecar in the Docker entrypoint. Migrations live in `migrations/` (top-level, not `docs/migrations/` — that path is wrong; move them). Numbered `00N_description.up.sql` + `00N_description.down.sql`. Applied at server startup before the HTTP listener binds.
|
||
- Why golang-migrate over Atlas/Goose: simplest, single binary, well-known, plays nicely with Supabase Postgres.
|
||
|
||
### Schema collisions
|
||
|
||
None. Paliad's existing 3 feedback tables don't overlap with anything in KanzlAI. The risk vector was `cases` vs. `akten` naming — resolved by going fully German.
|
||
|
||
---
|
||
|
||
## 4. Feature prioritization
|
||
|
||
KanzlAI shipped a lot in one session; not all of it was production-quality. Cherry-pick.
|
||
|
||
### Ports (P0 — first cutover)
|
||
|
||
- **Deadline rules + holidays + calculator** (KanzlAI: `holidays.go`, `deadline_calculator.go`, `deadline_rule_service.go`). Replaces Paliad's `internal/calc/deadline_rules.go` hardcoded data.
|
||
- **Akten CRUD + Parteien** (KanzlAI: `case_service.go`, `parties.go`). Foundation for everything below. Includes the office-scoped visibility logic from §2.
|
||
- **Frist management UI** (KanzlAI: `DeadlineList`, `DeadlineCalendarView`, traffic light cards). Extends current Fristenrechner with persistence.
|
||
|
||
### Ports (P1 — fast follow)
|
||
|
||
- **Termine + CalDAV sync** (KanzlAI: `appointment_service.go`, `caldav_service.go`). CalDAV is a real differentiator. **Mandatory fix during port:** encrypt CalDAV credentials at rest (audit §1.3).
|
||
- **Dashboard** (KanzlAI: `dashboard_service.go`, dashboard widgets). Replaces Paliad's logged-in landing.
|
||
|
||
### Ports (P2 — once foundation is stable)
|
||
|
||
- **AI Frist extraction** (KanzlAI: `ai_service.go`, document upload). Dependency on Anthropic key in Dokploy.
|
||
- **Notizen** (polymorphic notes table, KanzlAI Phase A backend). Cross-cutting; useful but not blocking.
|
||
|
||
### Drop entirely
|
||
|
||
- **Billing / RVG (`billing_rates.go`, `invoices.go`, `time_entries.go`)** — KanzlAI has handler stubs, no real impl. The audit (§8.2) calls out beA + RVG as table-stakes for *competing with RA-MICRO*. We're not competing with RA-MICRO. HLC has its own firm-wide billing. Don't pretend.
|
||
- **`reports.go`, `notifications.go`, `templates.go`, `tenant_handler.go`, `case_assignments.go`** — all aspirational stubs in KanzlAI. Drop.
|
||
- **Multi-tenancy infrastructure (`tenants`, `user_tenants`, X-Tenant-ID middleware)** — see §2; replaced by office-scoped visibility.
|
||
- **Reports/audit endpoints** beyond the basic `akten_events` audit log.
|
||
|
||
### Knowledge platform features that survive
|
||
|
||
Paliad's existing tools (Kostenrechner, Glossar, Gebührentabellen, Checklisten, Gerichte, Links, Downloads) all stay as-is. They sit alongside the new Aktenverwaltung features in the same sidebar.
|
||
|
||
### Knowledge platform features that change
|
||
|
||
- **Fristenrechner** evolves: same calculator UI, but now backed by the same DB as Frist management. Adds a "Zur Akte speichern" button that creates persistent Fristen on a chosen Akte.
|
||
- **Roadmap §2.3 UPC Rechtsprechungsübersicht** — dropped entirely (per task brief). Replaced by a curated link in the Link Hub pointing to youpc.org.
|
||
|
||
---
|
||
|
||
## 5. Roadmap update
|
||
|
||
### Changes to `docs/feature-roadmap.md`
|
||
|
||
**Rewrite the "What patholo Is NOT" section (lines 527–534).** New text:
|
||
|
||
```markdown
|
||
### What Paliad Is
|
||
|
||
Paliad is the all-in-one platform for HLC patent practice:
|
||
|
||
- **Knowledge platform** — curated content, practical tools, quick reference
|
||
(Glossar, Gebührentabellen, Checklisten, Gerichtsverzeichnis, Leitfäden, Links).
|
||
- **Aktenverwaltung** — Akten, Fristen, Termine, Parteien, Dokumente, Notizen,
|
||
Verlauf (audit trail). Office-scoped visibility with explicit collaborator
|
||
lists for cross-office teams. Personal calendar sync via CalDAV. AI-assisted
|
||
Frist extraction from uploaded court documents.
|
||
|
||
What Paliad is *not*:
|
||
|
||
- Not a billing tool — HLC has firm-wide billing infrastructure.
|
||
- Not a beA gateway — out of scope; lawyers use existing beA software.
|
||
- Not a document management system — SharePoint/netDocuments stay in their lane.
|
||
- Not a CMS — content lives in git, not a database with a CMS UI.
|
||
```
|
||
|
||
**Drop §2.3 UPC Rechtsprechungsübersicht entirely.** Replace with one Link Hub entry under "Recherche" pointing to `youpc.org/upc/rechtsprechung` (or wherever the curated overview lives). Save 14h of work; defer to youpc.org which already has the data.
|
||
|
||
**Update §4.2 Fristenkalender.** Currently P3, "8h, High impact". Promote to P0 — it's the entry point to the Aktenverwaltung features, not an optional add-on. Replace with: "Fristenkalender → see §8 Phase E (Frist management UI), Phase F (Termine + CalDAV sync)."
|
||
|
||
**Add new sections** (as a new top-level "Phase 0: Aktenverwaltung Foundation"):
|
||
|
||
```markdown
|
||
### 0.1 Akten (Matter Management) — office-scoped
|
||
### 0.2 Fristen (Persistent Deadline Management)
|
||
### 0.3 Termine + CalDAV Sync
|
||
### 0.4 Dashboard (Logged-in Landing)
|
||
### 0.5 AI-assisted Frist-Extraktion
|
||
### 0.6 Notizen
|
||
```
|
||
|
||
Each section should reference the per-phase task in §8 of *this* design doc rather than duplicating estimates.
|
||
|
||
**Update prioritized backlog table** (lines 463–483): insert the 6 Phase 0 features at the top with P0 priority. Demote 1.5 (Kostenrechner enhancements) to P1 — Aktenverwaltung beats Kostenrechner polish.
|
||
|
||
**Update Architecture Notes "Data Strategy" (line 489).** Currently says "Most Phase 1 and 2 features use static JSON data." Add: "Phase 0 features (Aktenverwaltung) use Supabase tables with office-scoped RLS. The split is: reference data + curated content = git/JSON; user-generated data = DB."
|
||
|
||
---
|
||
|
||
## 6. UI integration
|
||
|
||
### Decision: One sidebar, six grouped sections
|
||
|
||
Current Paliad sidebar is flat. KanzlAI has its own React sidebar with cases-first navigation. Merge into a single grouped sidebar:
|
||
|
||
```
|
||
PALIAD (logo, lime green)
|
||
|
||
— ÜBERSICHT —
|
||
Dashboard
|
||
|
||
— ARBEIT —
|
||
Akten
|
||
Fristen
|
||
Termine
|
||
|
||
— WERKZEUGE —
|
||
Kostenrechner
|
||
Fristenrechner ← stays as a quick-calc tool, distinct from /fristen
|
||
Gebührentabellen
|
||
|
||
— WISSEN —
|
||
Glossar
|
||
Checklisten
|
||
Gerichtsverzeichnis
|
||
Leitfäden (future, per current roadmap §2.1)
|
||
|
||
— RESSOURCEN —
|
||
Downloads
|
||
Nützliche Links
|
||
|
||
— FOOTER —
|
||
DE / EN
|
||
user@example.com → Logout
|
||
```
|
||
|
||
Rationale:
|
||
- **"Übersicht"** (Dashboard) is the new home for logged-in users. The current `/` marketing-ish landing becomes the unauthenticated welcome screen.
|
||
- **"Arbeit"** = the user's active workload. This is what KanzlAI provided; it's now the top of the new value stack.
|
||
- **"Werkzeuge"** = stateless calculators / lookups. Including Fristenrechner here as a quick scratch tool, separate from `/fristen` which is the persistent Fristen list. (Yes, two deadline-related routes — they serve different jobs.)
|
||
- **"Wissen"** = curated content.
|
||
- **"Ressourcen"** = files + external links.
|
||
|
||
### Navigation in detail
|
||
|
||
- `/` — unauthenticated landing OR redirects authenticated users to `/dashboard`.
|
||
- `/dashboard` — Dashboard (KanzlAI port).
|
||
- `/akten`, `/akten/neu`, `/akten/[id]`, `/akten/[id]/{verlauf|fristen|termine|dokumente|parteien|notizen}` — Akten list + detail with sub-pages.
|
||
- `/fristen`, `/fristen/neu`, `/fristen/[id]`, `/fristen/kalender` — persistent Fristen.
|
||
- `/termine`, `/termine/neu`, `/termine/[id]`, `/termine/kalender` — Termine.
|
||
- `/einstellungen/caldav` — per-user CalDAV configuration.
|
||
- `/onboarding` — first-login office/practice-group/role setup (Phase D).
|
||
- `/tools/fristenrechner` — stays at current path (don't break links). Adds "Zur Akte speichern" button.
|
||
- All other knowledge routes unchanged.
|
||
|
||
### Visual language
|
||
|
||
- Lime accent (`#c6f41c`) stays — it's the brand. Used for active nav, primary CTAs, traffic-light "go", new-since-last-visit badges.
|
||
- Traffic light colors for Fristen: red `#ef4444` (überfällig), amber `#f59e0b` (diese Woche), green `#22c55e` (OK). KanzlAI's choices, keep them.
|
||
- Type colors for Termine: hearing=blue, meeting=violet, consultation=emerald, deadline_hearing=amber. KanzlAI's choices, keep them.
|
||
- Akten list: a small office badge per row (Munich, Düsseldorf, etc.) so cross-office collaborators can scan visibility origin at a glance.
|
||
- Sidebar: existing Paliad styling, with section headers (small caps, low contrast) added.
|
||
|
||
### Frontend rewrite scope
|
||
|
||
Pages to build in Bun TSX:
|
||
- `dashboard.tsx` (1 page)
|
||
- `akten.tsx`, `akten-detail.tsx`, `akten-neu.tsx` (3 pages, plus sub-routes via Go mux)
|
||
- `fristen.tsx`, `fristen-detail.tsx`, `fristen-neu.tsx`, `fristen-kalender.tsx` (4 pages)
|
||
- `termine.tsx`, `termine-detail.tsx`, `termine-neu.tsx`, `termine-kalender.tsx` (4 pages)
|
||
- `einstellungen-caldav.tsx` (1 page)
|
||
- `onboarding.tsx` (1 page — first-login form)
|
||
|
||
Per-page client-side TS bundles in `src/client/` for interactive bits (calendar nav, mark complete, collaborator-picker autocomplete, modal forms). HTML-first: forms POST and reload by default; JS enhances where it improves UX (calendar pagination, optimistic mark-complete).
|
||
|
||
No react-query, no Tailwind v4. Use existing `global.css` patterns.
|
||
|
||
---
|
||
|
||
## 7. Deployment
|
||
|
||
### Decision: Single Dokploy compose, retire `kanzlai.msbls.de`
|
||
|
||
- **Production target**: existing Paliad compose `Zx147ycurfYagKRl_Zzyo`. No new infra.
|
||
- **Domains on the compose**:
|
||
- `paliad.de` — primary
|
||
- `patholo.de` — legacy redirect (already configured)
|
||
- `patholo.msbls.de` — internal preview (already configured)
|
||
- **Add: `kanzlai.msbls.de` → 301 redirect to `paliad.de`** (Phase J)
|
||
- **KanzlAI Dokploy app** — stop and delete after Phase J cutover. Free up the resources.
|
||
- **KanzlAI Gitea repo** (`m/KanzlAI-mGMT`) — archive (set read-only) after cutover. Keep for git history reference.
|
||
|
||
### Build and deploy
|
||
|
||
- Existing flow stays: push to `main` on `m/paliad` → Gitea webhook → Dokploy auto-deploy.
|
||
- Dockerfile changes: add migration step to entrypoint (run `migrate up` against `DATABASE_URL` before starting the HTTP server).
|
||
- New env vars in Dokploy:
|
||
- `DATABASE_URL` (youpc Supabase Postgres conn string)
|
||
- `SUPABASE_SERVICE_KEY` (for storage access during AI extraction)
|
||
- `ANTHROPIC_API_KEY` (for AI extraction; AI features stay disabled if unset)
|
||
- `CALDAV_ENCRYPTION_KEY` (32-byte AES key for credential-at-rest encryption)
|
||
- Existing env vars unchanged: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `GITEA_TOKEN`, `PORT`.
|
||
|
||
### Cutover sequence
|
||
|
||
Single deployment (no parallel run). Because KanzlAI has zero production users, the cutover is just:
|
||
|
||
1. Apply migrations (Phase A).
|
||
2. Ship phases B–H to `paliad.de`.
|
||
3. Verify functionality.
|
||
4. Flip `kanzlai.msbls.de` to 301-redirect to `paliad.de` (Phase J).
|
||
5. Stop KanzlAI Dokploy app.
|
||
|
||
If KanzlAI ever gets users before cutover, switch to a parallel-run-with-data-migration model. Right now, no users = no data migration.
|
||
|
||
---
|
||
|
||
## 8. Migration plan (phased task breakdown)
|
||
|
||
Each phase is sized for one focused coder session with clear acceptance criteria. Effort estimates assume Sonnet implementation with Opus design review where flagged.
|
||
|
||
### Phase A — Database foundation, visibility model & migration tooling (~8h)
|
||
|
||
**Scope:**
|
||
- Create `paliad` schema in youpc Supabase instance.
|
||
- Add `golang-migrate` to the Docker entrypoint.
|
||
- Move existing Paliad migrations (`docs/migrations/00{1,2,3}.sql`) → `migrations/00{1,2,3}_*.up.sql` + `.down.sql`. Re-namespace tables into `paliad.*`.
|
||
- Create `paliad.users` table with `office`, `practice_group`, `role` columns per §2.
|
||
- Port KanzlAI tables → `paliad.*` migrations with German names: `proceeding_types`, `deadline_rules`, `holidays`, `akten` (renamed from `cases`, with `owning_office` + `collaborators uuid[]` + `firm_wide_visible` columns), `parteien`, `fristen`, `termine`, `dokumente`, `akten_events`, `notizen`. Drop `tenant_id` everywhere; add `created_by` FK to `paliad.users.id`.
|
||
- Create `paliad.can_see_akte(akte_id uuid)` SQL function per §2.
|
||
- RLS policies per §2: `akten_select/insert/update/delete` using the visibility predicate; child-row policies (fristen, termine, parteien, dokumente, akten_events, notizen) using `paliad.can_see_akte(akte_id)`. Reference tables RLS-on but `USING (true)`.
|
||
- Indexes per audit §3.3 + visibility queries: `(owning_office)` on akten, `(status, due_date)` on fristen, `(start_at)` on termine, `(akte_id, created_at)` on akten_events, `(status)` on akten, GIN index on `collaborators` for `= ANY` lookups.
|
||
- Seed: 6 proceeding types, 32 UPC + 4 ZPO rules, 68 holidays (port directly from KanzlAI seed).
|
||
- Seed `paliad.users` with cronus's test user as `admin` for local dev (real users register via the app).
|
||
|
||
**Acceptance:**
|
||
- `\dt paliad.*` shows ≥12 tables (including `users`).
|
||
- RLS enabled on all (verified via `pg_class.relrowsecurity`).
|
||
- Visibility predicate verified with 3 test users in 2 offices: each sees only their own office's Akten + any with their UUID in collaborators + any with `firm_wide_visible=true`.
|
||
- Migrations run cleanly up + down.
|
||
- Seed data present.
|
||
- Server starts after `migrate up` runs in entrypoint.
|
||
|
||
**Owner suggestion:** coder. Opus review the RLS policy + visibility predicate before merge — this is the security-critical layer.
|
||
|
||
---
|
||
|
||
### Phase B — Backend foundation (sqlx, services, no UI yet) (~8h)
|
||
|
||
**Scope:**
|
||
- Add `internal/db/` package (sqlx connection pool with table references qualified to `paliad.*` — don't rely on session-level `search_path` per audit §2.8).
|
||
- Port `internal/services/holidays.go` from KanzlAI (with `sync.RWMutex` fix per audit §1.6).
|
||
- Port `internal/services/deadline_calculator.go` (UUID-based; works from DB rules).
|
||
- Port `internal/services/deadline_rule_service.go` (read from DB).
|
||
- Add `internal/services/user_service.go` — bootstrap `paliad.users` row from `auth.users`, get current user from request context, list users.
|
||
- Add `internal/services/akte_service.go` (formerly `case_service.go`). Visibility-aware: `ListVisibleForUser(ctx, userID)` enforces the §2 predicate at app layer (defense in depth alongside RLS). Create/Update/Delete enforce role + office checks.
|
||
- Port `internal/services/parteien_service.go`.
|
||
- Tests: deadline calculator round-trip, holiday adjustment, Akte visibility (3-user 2-office scenario), Akte CRUD happy-path with role enforcement.
|
||
|
||
**Acceptance:**
|
||
- `GET /api/deadline-rules` returns rules from DB.
|
||
- `POST /api/fristen/calculate` works with DB rules (proves Phase A schema ↔ Phase B services wiring).
|
||
- `GET/POST /api/akten` works (no UI yet — verified via curl with 3 test users).
|
||
- Visibility verified end-to-end: User A (Munich associate) cannot see User B (Düsseldorf associate)'s Akte unless added as collaborator or `firm_wide_visible=true`.
|
||
- All ported tests pass.
|
||
- No regressions in existing Paliad endpoints.
|
||
|
||
**Owner suggestion:** coder. Opus review the visibility enforcement code paths.
|
||
|
||
---
|
||
|
||
### Phase C — Migrate existing Fristenrechner to DB-backed rules (~3h)
|
||
|
||
**Scope:**
|
||
- Delete `internal/calc/deadline_rules.go` hardcoded data.
|
||
- Update `internal/handlers/fristenrechner.go` to call DB-backed services from Phase B.
|
||
- Verify existing `/tools/fristenrechner` UI is byte-identical (no UX regression).
|
||
- Update `internal/calc/deadlines.go`: delete logic that's now in `services/deadline_calculator.go`; thin shim if needed for the existing JSON shape.
|
||
|
||
**Acceptance:**
|
||
- `/tools/fristenrechner` page identical UX, same dropdowns, same calc results.
|
||
- No hardcoded rules in `internal/calc/`.
|
||
- Existing fristenrechner tests pass against new code path.
|
||
|
||
**Owner suggestion:** coder.
|
||
|
||
---
|
||
|
||
### Phase D — Akten CRUD + onboarding + collaborator UI (~8h)
|
||
|
||
**Scope:**
|
||
- Backend: handlers for `GET/POST/PATCH/DELETE /api/akten`, `GET/POST /api/akten/{id}/parteien`, `GET /api/users` (for collaborator-picker autocomplete, returns minimal user list).
|
||
- Backend: `POST /api/onboarding` — accepts office/practice_group/role for the current `auth.uid()`, creates the `paliad.users` row.
|
||
- Backend: `PATCH /api/akten/{id}/collaborators` (add/remove), `PATCH /api/akten/{id}/firm-wide-visible` (partner-only — checked via role).
|
||
- Frontend (TSX):
|
||
- `onboarding.tsx` — first-login form. Middleware-detected: if `paliad.users` row missing, redirect here on any protected route.
|
||
- `akten.tsx` — list scoped to visible Akten. Office badge per row. Filter by office (own / firm-wide / all-visible).
|
||
- `akten-neu.tsx` — form. `owning_office` defaults to user's own office; admin can pick any.
|
||
- `akten-detail.tsx` — header + tabs. Header shows office badge + collaborator avatars + firm-wide-visible flag. Collaborator-picker modal for adding/removing. Partner-only "Firm-wide visible" toggle.
|
||
- Sidebar: add "Arbeit" group with Akten as the only entry initially.
|
||
- Per-page client TS for inline edit, delete confirm, collaborator autocomplete.
|
||
|
||
**Acceptance:**
|
||
- New user signs up → redirected to `/onboarding` → completes form → redirected to `/dashboard`.
|
||
- Lawyer creates Akte → automatically owns it under their office.
|
||
- Lawyer in another office cannot see the Akte unless added as collaborator.
|
||
- Partner toggles `firm_wide_visible` → all users now see it.
|
||
- Audit trail: each mutation creates an `akten_events` row.
|
||
- Empty states are friendly (German Umlaute correct per audit §2.10).
|
||
|
||
**Owner suggestion:** coder. Opus review the visibility-related UI flows (collaborator picker, firm-wide toggle).
|
||
|
||
---
|
||
|
||
### Phase E — Persistent Frist management UI (~6h)
|
||
|
||
**Scope:**
|
||
- Backend: handlers for `GET /api/fristen` (all visible), `GET/POST /api/akten/{id}/fristen`, `PATCH /api/fristen/{id}`, `PATCH /api/fristen/{id}/complete`, `DELETE /api/fristen/{id}`. All visibility-enforced via the parent Akte.
|
||
- New TSX pages: `fristen.tsx` (list with status filter — überfällig/diese_woche/alle), `fristen-neu.tsx`, `fristen-detail.tsx`, `fristen-kalender.tsx` (month grid).
|
||
- Traffic light cards on `fristen.tsx` + on `akten-detail.tsx` (Fristen tab).
|
||
- "Zur Akte speichern" button on existing Fristenrechner: select target Akte from dropdown of visible Akten, persists calculated Fristen as draft.
|
||
|
||
**Acceptance:**
|
||
- Lawyer creates Akte, adds Fristen manually OR via Fristenrechner "Zur Akte speichern".
|
||
- List filterable by status. Calendar view navigable by month.
|
||
- Mark-complete works, persists, updates traffic light counts.
|
||
- Lawyer cannot see Fristen on Akten they don't have visibility to.
|
||
|
||
**Owner suggestion:** coder.
|
||
|
||
---
|
||
|
||
### Phase F — Termine + CalDAV sync (~8h)
|
||
|
||
**Scope:**
|
||
- Backend: `termin_service.go`, handlers for CRUD. Visibility via parent Akte.
|
||
- Backend: `caldav_service.go` ported from KanzlAI **with credential encryption fix** (audit §1.3): use `CALDAV_ENCRYPTION_KEY` env var with AES-GCM at rest. Never return password in API responses.
|
||
- New TSX pages: `termine.tsx`, `termine-neu.tsx`, `termine-detail.tsx`, `termine-kalender.tsx`, `einstellungen-caldav.tsx`.
|
||
- Per-user CalDAV config (one entry per `auth.uid()`, stored in new `paliad.user_caldav_config` table).
|
||
- Background sync goroutine per user with active config (pulled at server startup, refreshed when config changes).
|
||
- Sync scope: only sync Termine on Akten the user can see (visibility predicate applies to outbound sync too).
|
||
|
||
**Acceptance:**
|
||
- User configures CalDAV in settings, password is encrypted in DB.
|
||
- User creates a Termin, it appears in their external calendar within 60s.
|
||
- External calendar event edited → reflected in Paliad on next sync cycle.
|
||
- Deleting CalDAV config purges credentials.
|
||
- User cannot leak Termine to their personal calendar from Akten they don't have visibility to.
|
||
|
||
**Owner suggestion:** coder, Opus design review of the encryption scheme + sync conflict resolution + visibility-aware sync scope.
|
||
|
||
---
|
||
|
||
### Phase G — Dashboard (~4h)
|
||
|
||
**Scope:**
|
||
- Backend: `dashboard_service.go` ported and simplified (no tenant; uses visibility predicate). Returns `frist_summary`, `akten_summary`, `upcoming_fristen` (7d), `upcoming_termine` (7d), `recent_activity` — all scoped to what the user can see.
|
||
- New TSX page: `dashboard.tsx` — server-rendered initial paint (no client-side fetch + skeleton, per audit §2.3 critique).
|
||
- Update `/` route: redirect authenticated users to `/dashboard`; serve current marketing-ish landing only for unauthenticated visitors (or remove entirely if HLC sees no value in the unauthenticated landing).
|
||
- Update sidebar "Übersicht → Dashboard" entry.
|
||
|
||
**Acceptance:**
|
||
- Authenticated user lands on `/dashboard` with zero client-side waterfall.
|
||
- Traffic lights link to `/fristen?status=...`.
|
||
- Upcoming items link to detail pages.
|
||
- Recent activity shows last 10 akten_events with author + timestamp, scoped to visible Akten.
|
||
|
||
**Owner suggestion:** coder.
|
||
|
||
---
|
||
|
||
### Phase H — AI Frist-Extraktion (~4h)
|
||
|
||
**Scope:**
|
||
- Backend: `ai_service.go` ported from KanzlAI. Anthropic SDK call with PDF document blocks + tool-forced structured output.
|
||
- Backend: document upload → Supabase Storage, then AI extraction triggered. Visibility check on parent Akte before processing.
|
||
- Frontend: document upload component on `akten-detail.tsx` "Dokumente" tab.
|
||
- Extraction result: review screen with confidence scores + source quotes; user confirms which Fristen to persist.
|
||
- Feature flag: AI tab/buttons hidden if `ANTHROPIC_API_KEY` is unset.
|
||
- Per-user rate limit on AI endpoint (fixes audit §1.7 by keying off `auth.uid()`, not spoofable headers).
|
||
|
||
**Acceptance:**
|
||
- Upload a UPC Statement of Defence PDF → AI extracts Fristen with rule references → user confirms → persisted as draft Fristen on the Akte.
|
||
- No AI calls on uploads to other Akten (visibility-checked).
|
||
- Rate limit caps abuse.
|
||
|
||
**Owner suggestion:** coder.
|
||
|
||
---
|
||
|
||
### Phase I — Notizen (polymorphic) (~4h)
|
||
|
||
**Scope:**
|
||
- Backend: `notizen` table migration (polymorphic FK with CHECK constraint per KanzlAI Phase A pattern), `notiz_service.go`, handlers. Visibility inherited from parent Akte (when notiz attaches to Akte/Frist/Termin/AkteEvent).
|
||
- Frontend: shared `NotizenList` TSX component, embedded on Akte / Frist / Termin / AkteEvent detail pages.
|
||
|
||
**Acceptance:**
|
||
- User adds Notiz to an Akte → visible on Akte detail.
|
||
- User adds Notiz to a Frist → visible on Frist detail.
|
||
- Edit/delete works.
|
||
- Visibility inherited correctly.
|
||
|
||
**Owner suggestion:** coder.
|
||
|
||
---
|
||
|
||
### Phase J — Roadmap rewrite + KanzlAI retirement (~3h)
|
||
|
||
**Scope:**
|
||
- Rewrite `docs/feature-roadmap.md` per §5 of this doc.
|
||
- Add `kanzlai.msbls.de` domain to Paliad Dokploy compose with 301 redirect rule (Traefik label or middleware).
|
||
- Stop and delete KanzlAI Dokploy app.
|
||
- Archive `m/KanzlAI-mGMT` Gitea repo (set to read-only / archived).
|
||
- Update `mai.projects`: optionally merge `kanzlai` project into `paliad` (decide: keep history separate or unify).
|
||
- Memory: write episode in `paliad` group documenting the consolidation, supersede KanzlAI episodes.
|
||
- Drop `kanzlai` schema from Supabase: `DROP SCHEMA kanzlai CASCADE` after final verification that no Paliad code references it.
|
||
|
||
**Acceptance:**
|
||
- `kanzlai.msbls.de` redirects to `paliad.de` with 301.
|
||
- KanzlAI Dokploy app gone.
|
||
- KanzlAI repo archived.
|
||
- Roadmap doc reflects the new strategy.
|
||
- `kanzlai` schema dropped.
|
||
|
||
**Owner suggestion:** head + cronus together (mostly ops, a bit of writing).
|
||
|
||
---
|
||
|
||
### Total effort
|
||
|
||
**~56 hours of focused implementation across 10 phases** (revised up from 52h: Phase A +2h for visibility model, Phase D +2h for onboarding + collaborator UI), plus this design review (~3h) = **~59h total**. At 1–2 phases per day with one coder, this completes in **2–3 working weeks**.
|
||
|
||
---
|
||
|
||
## 9. Risks & unknowns
|
||
|
||
### Risks
|
||
|
||
1. **KanzlAI's audit issues must not be ported as-is.** The audit identified 7 critical and 13 important issues. Each port phase is responsible for fixing the issues relevant to its code: Phase A enforces real (not permissive) RLS policies (audit §2.13), Phase B fixes HolidayService race + search_path (§1.6, §2.8) and the tenant-isolation pattern (§1.1, replaced by visibility predicate), Phase F fixes CalDAV plaintext (§1.3), Phase H fixes the rate-limiter spoofability (§1.7). Every backend phase implements proper error handling (§1.5) + length limits + pagination (§2.1, §2.2). Track these in a checklist per phase.
|
||
|
||
2. **React → Bun TSX rewrite friction.** KanzlAI's frontend uses react-query, react-day-picker, modal libs, complex client state. Paliad's stack uses server-rendered TSX + small per-page client TS. Some patterns translate awkwardly. Mitigation: HTML-first design, JS only where it materially improves UX. If a feature *requires* a heavy interactive component (e.g., date picker), prefer native `<input type="date">` over importing a library.
|
||
|
||
3. **Visibility model is the security-critical layer.** Office-scoped RLS with collaborator override is more complex than either "everyone sees everything" or pure tenant-id. Bugs here leak Akten across offices. Mitigation: Phase A and Phase B both have Opus design reviews on the visibility predicate; Phase B includes a 3-user 2-office integration test as an acceptance gate; the predicate lives in a single SQL function (`paliad.can_see_akte`) reused by every RLS policy and mirrored at the app layer for defense in depth. Add a pen-test pass (or at minimum a security-review skill run) before Phase J.
|
||
|
||
4. **Email domain transition risk.** Whitelist `[@hoganlovells.com, @hlc.com, @hlc.de]`. Risk: HLC picks a TLD we didn't anticipate (`@hlc.law`?). Mitigation: keep the whitelist in env config, not hardcoded. Trivial to update.
|
||
|
||
5. **CalDAV encryption key rotation.** If `CALDAV_ENCRYPTION_KEY` is ever lost or rotated, all stored credentials become unreadable. Mitigation: document the recovery posture (users re-enter CalDAV password from their existing software), commit to never rotating without a re-encrypt migration.
|
||
|
||
6. **KanzlAI-derived German Umlaut typos** in audit §2.10. Don't carry them forward. Style check during each frontend port phase.
|
||
|
||
7. **Outlook / Exchange CalDAV is not where HLC lawyers live.** HLC primarily uses Outlook + Exchange; Exchange's CalDAV support is limited or off by default. Phase F ships with CalDAV only (verified against `dav.msbls.de` and Apple iCloud as KanzlAI did). **Long-term plan: add an EWS / Microsoft Graph API backend behind the same sync abstraction.** That's a follow-on phase (call it Phase K), not blocking the v1 cutover. For HLC users on Outlook, Phase F will work for *manual* iCal export; full bidirectional sync waits for Phase K.
|
||
|
||
8. **No load testing on KanzlAI's calc-heavy paths.** AI extraction in particular can be slow + expensive. Phase H adds per-user rate limits before going live (per-user via `auth.uid()`, not header).
|
||
|
||
### Unknowns
|
||
|
||
- **Practice-group scoping intent.** §2 makes office the access boundary and treats `practice_group` as filter-only metadata. Confirm with a HLC partner: are there matters where Patents Litigation must be walled off from Patents Prosecution? If yes, schema is ready (extend the predicate); if no, status quo.
|
||
- **AI extraction quality on real HLC documents.** KanzlAI's test fixtures were synthetic. Real UPC Statements of Defence are messier. Phase H should include a manual test pass on 3–5 real (anonymized) HLC documents before declaring done.
|
||
- **Outlook integration urgency.** Phase K (EWS / Graph) could be ahead of Phase J if a partner says CalDAV-only is a non-starter at HLC. Decide at end of Phase F based on internal feedback.
|
||
- **Naming check with a HLC partner.** "Akten" / "Aktenverwaltung" is the German legal-practice norm — confirm one final time before the strings are baked in across the UI.
|
||
|
||
---
|
||
|
||
## 10. Open question for the head
|
||
|
||
Once this design is approved, who implements?
|
||
|
||
- **Option 1: I (cronus) implement Phases A–J as coder.** I have the deepest context from this design work and the source repos. Single context, no handoff loss. Likely faster overall.
|
||
- **Option 2: Hand off to a separate coder worker (e.g., pike, knuth, ritchie) per phase.** Better parallelism if multiple phases can run in parallel. But Phases A → B → C → D have hard dependencies; only F, G, H, I can run in any order after E.
|
||
- **Option 3: Hybrid — I implement Phases A–C (foundation, including the security-critical visibility model), hand off D–I to coder workers in parallel where possible, take J myself for the wrap-up.**
|
||
|
||
**My recommendation: Option 3.** Foundation needs design context (cronus is the carrier), and the visibility model in §2 is too security-critical to hand off cold. Phases D–I are mechanical ports of well-defined services; coder workers can run them with the design doc + KanzlAI source as the brief. Phase J is the easy victory lap.
|
||
|
||
---
|
||
|
||
## 11. Post-Integration Status (added 2026-04-17)
|
||
|
||
Recorded after Phase J documentation pass on branch `mai/ritchie/phase-j-roadmap-rewrite`.
|
||
|
||
### Shipped phases
|
||
|
||
| Phase | Scope | Status | Merge |
|
||
|---|---|---|---|
|
||
| A | Database foundation, visibility model, migration tooling | ✅ Shipped | `1b2ef28` (2026-04-16) |
|
||
| B | sqlx pool, services, Akten/Frist endpoints | ✅ Shipped | `bcc4939` (2026-04-16) |
|
||
| C | Fristenrechner → DB-backed | ✅ Shipped | `d1909c7` (2026-04-16) |
|
||
| D | Akten CRUD + onboarding + collaborator UI | ✅ Shipped | `4296da5` (2026-04-16) |
|
||
| E | Persistent Frist management UI | ✅ Shipped | `316dc9f` (2026-04-16) |
|
||
| F | Termine + CalDAV sync (AES-GCM at rest) | ✅ Shipped | `b56ef66` (2026-04-17) |
|
||
| G | Dashboard (server-rendered) | ✅ Shipped | `b79ef25` (2026-04-16) |
|
||
| H | AI Frist-Extraktion | ⏸ **Deferred** | branch `mai/ritchie/phase-h-ai-deadline` — not merged |
|
||
| I | Notizen (polymorphic service + UI) | ⬜ Pending | Schema only (migrations 005/007); service and UI not started |
|
||
| J | Roadmap rewrite + KanzlAI retirement | 🟡 **Docs only** — infra pending | see below |
|
||
|
||
### Phase H — deferred (not cancelled)
|
||
|
||
Decision by m on 2026-04-16: *"We don't want Anthropic API. We put this off for a while."* The work on branch `mai/ritchie/phase-h-ai-deadline` (commit `f539102`) covers the extraction path end-to-end, but will not be merged until the Anthropic API decision is revisited. The Dokumente tab on Akten detail stays as a "Kommt bald" placeholder. No `ANTHROPIC_API_KEY` on Dokploy.
|
||
|
||
Document upload + Supabase Storage alone (without AI) remains an open question — potentially worth shipping as a standalone Dokumente feature even with AI deferred.
|
||
|
||
### Phase I — pending
|
||
|
||
`paliad.notizen` table with polymorphic FK + CHECK constraint and RLS is already in place (migrations 005 and 007). The service (`notiz_service.go`), handlers, and the shared `NotizenList` TSX component are not yet built. Picks up as ~4h of focused work when the cross-cutting notes become the next friction point.
|
||
|
||
### Phase J — partial (documentation done; infra retirement pending)
|
||
|
||
**Done in this Phase J pass (2026-04-17, branch `mai/ritchie/phase-j-roadmap-rewrite`):**
|
||
|
||
- `docs/feature-roadmap.md` rewritten per §5 of this doc: all-in-one positioning, Phase 0 Aktenverwaltung section with completed items, "What Paliad Is" replaces "What patholo Is NOT", dropped §2.3 UPC Rechtsprechung (youpc.org link covers it), updated prioritized backlog with done markers, Phase H marked deferred, Architecture Notes data-strategy updated for `paliad` schema + office-scoped RLS.
|
||
- `.claude/CLAUDE.md` refreshed with current feature list, env vars (`DATABASE_URL`, `CALDAV_ENCRYPTION_KEY`), and phase status.
|
||
- `README.md` refreshed with current feature list, full migration inventory, env vars, and project layout.
|
||
- This "Post-Integration Status" section added.
|
||
|
||
**Still pending — requires head + m coordination (NOT in this Phase J task scope):**
|
||
|
||
- Add `kanzlai.msbls.de` domain to Paliad Dokploy compose with 301 redirect rule.
|
||
- Stop and delete the KanzlAI Dokploy app.
|
||
- Archive the `m/KanzlAI-mGMT` Gitea repo (set read-only / archived).
|
||
- Merge-or-separate decision for `mai.projects.kanzlai` vs. `mai.projects.paliad`.
|
||
- `DROP SCHEMA kanzlai CASCADE` on youpc Supabase after final verification.
|
||
- Memory: write a consolidation episode in the `paliad` group and supersede KanzlAI episodes (noted as a followup for m).
|
||
|
||
These are ops actions with real blast radius (public domain cutover, shared-DB schema drop, repo archival) and should not run unattended from a documentation task.
|
||
|
||
### Email gate: still hardcoded
|
||
|
||
The design §2 specified an env-configurable whitelist `[@hoganlovells.com, @hlc.com, @hlc.de]`. Current code (`internal/handlers/auth.go:115`) still hardcodes `hoganlovells.com`. Move to env config before HLC emails come online — trivial change, just hasn't happened yet.
|
||
|
||
### Visibility model: verified in use across shipped phases
|
||
|
||
The `paliad.can_see_akte(akte_id)` predicate is the single source of truth and is reused by every RLS policy and mirrored by `AkteService.GetByID` at the application layer. `FristService`, `TerminService`, and `ParteienService` all route through `AkteService.GetByID` before operating on their own rows. No duplication. Architecture invariant held through Phases D, E, F.
|
||
|
||
### CalDAV: manual Outlook plan still open
|
||
|
||
Phase F verified CalDAV against `dav.msbls.de` and Apple iCloud. HLC lives on Outlook + Exchange where CalDAV support is limited or off by default. The Phase K plan (EWS / Microsoft Graph backend behind the same sync abstraction) remains the fallback. Reassess after first real HLC user feedback.
|
||
|
||
---
|
||
|
||
*End of design.*
|