Commit Graph

18 Commits

Author SHA1 Message Date
mAi
8f6cee5a83 chore(t-paliad-194): delete paliad-side paliadin skill bundle (SoT moved to m/mAi)
Per m's 2026-05-13 decision (m/mAi#207 §13 Q4): the paliadin SKILL.md
and references/sql-recipes.md are now owned by aichat. The aichat repo
already has the equivalents committed at skills/aichat/paliadin/ on
mai/darwin/issue-207-aichat (verified before this commit). Aichat's
own deploy doc handles installation on mRiver.

Deleted:
  scripts/skills/paliadin/SKILL.md
  scripts/skills/paliadin/references/sql-recipes.md
  scripts/install-paliadin-skill

Legacy LocalPaliadinService / RemotePaliadinService still depend on
~/.claude/skills/paliadin/ being present on whichever host they run
against. Until those paths retire (Phase C / Q15), operators install
the skill manually from m/mAi/skills/aichat/paliadin/.

CLAUDE.md updated:
  - PALIADIN_SESSION_PREFIX row points readers at m/mAi for the skill
    SoT and notes the legacy paths still expect a manual install.
  - New env-var rows for PALIADIN_BACKEND / AICHAT_URL / AICHAT_TOKEN /
    AICHAT_PERSONA so the operator runbook for the Phase B flip is
    self-contained.
2026-05-15 03:03:49 +02:00
m
e75a71fb34 fix(t-paliad-155): spawn claude pane in paliad repo root for project MCPs
claude in the shim's tmux pane was being launched from $HOME, so it
loaded only global MCPs (mai, mai-memory, mgeo) and missed the
project-scoped Supabase MCP at /home/m/dev/paliad/.mcp.json. SKILL.md's
SQL recipes therefore had no DB tool — m saw 'no DB access' on every
real Paliadin turn.

Fix: tmux new-window -c $CLAUDE_CWD when spawning the pane. New env
var PALIADIN_REMOTE_CWD (default /home/m/dev/paliad) lets a host
override the path if the repo lives elsewhere; shim fast-fails with
exit 3 if the directory doesn't exist.

CLAUDE.md updated. Verified by spawning a fresh session via the shim
and inspecting #{pane_current_path}.
2026-05-08 13:03:50 +02:00
m
97a412498d feat(t-paliad-155): real Claude SKILL.md + per-user tmux session
Move Paliadin's persona + response protocol from a tmux-keystroke-injected
system prompt into a real Claude skill at ~/.claude/skills/paliadin/SKILL.md
(repo source: scripts/skills/paliadin/SKILL.md, install script:
scripts/install-paliadin-skill). Claude's skill router auto-matches the
[PALIADIN:<uuid>] envelope on every turn, so the protocol contract
survives /clear, fresh sessions, and pane restarts — root-cause fix for
the post-/clear stuck-spinner that triggered this task.

Per-user tmux session keying: each Paliad user gets a session named
<prefix>-<userid8> (first 8 hex chars of UUID). One persistent session
per user, conversation history accumulates per visit, ResetSession kills
the session entirely. Health-check cache becomes per-session.

Service-side simplifications:
- paliadin_prompt.go (paliadinSystemPrompt) deleted; trailer parser stays
  in paliadin.go.
- paliadin_remote.go: ensureBootstrapped removed; healthGate takes a
  session arg + caches per-key; ResetSession derives session from UserID
  and shells out to 'reset <session>'.
- paliadin.go (LocalPaliadinService): per-user pane cache, ensurePane
  takes UserID, no more in-process system-prompt send.
- Paliadin interface: ResetSession now takes UserID.

Shim refactor (scripts/paliadin-shim):
- All verbs accept the tmux session as their first positional arg.
- 'bootstrap' verb removed (skill replaces it).
- 'reset' kills the named session via tmux kill-session.
- Session name validated against [A-Za-z0-9_.-]{1,64}.

Env var rename: PALIADIN_TMUX_SESSION -> PALIADIN_SESSION_PREFIX (semantic
shift from literal session name to per-user prefix); CLAUDE.md updated.

Tests cover per-session health caching, session-name derivation,
ResetSession kill-session shape, and health-cache eviction on reset.
2026-05-08 12:42:57 +02:00
m
8d714dd95e fix(t-paliad-146): gate Paliadin to owner email in code, drop PALIADIN_ENABLED
m's call (2026-05-07 21:52): "remove the export variable, that is bad
form. It should be connected only to my account."

The PALIADIN_ENABLED env var was a deploy-time toggle: easy to
mis-flip, splits prod/dev behaviour, and reads as "could be turned on
for anyone." Replaced with a per-request gate in code:

  services.PaliadinOwnerEmail = "matthias.siebels@hoganlovells.com"

handlers/paliadin.go now gates every entry point through
requirePaliadinOwner, which looks up paliad.users.email by the caller's
UUID and returns 404 (not 403 — pretend the route doesn't exist) for
anyone else.

Routes register unconditionally; the gate is in the code, not the
deploy. main.go wires PaliadinService whenever DATABASE_URL is set and
logs the owner identity at boot. CLAUDE.md drops the PALIADIN_ENABLED
row and gains an explanatory note about the in-code gate.

Sidebar entries (Paliadin under Übersicht; Paliadin Monitor under
Admin) now render with display:none, revealed by sidebar.ts after
/api/me confirms the caller's email matches PALIADIN_OWNER_EMAIL —
same fail-closed pattern the Admin group already uses.

Side-effect for ops: paliad.de production now serves the routes too,
but only to m, and only successfully if the host has tmux + claude
in PATH (which Dokploy doesn't). m hitting /paliadin from prod gets a
"tmux unavailable" — clear failure mode, not a security concern.

One new test (TestPaliadinOwnerEmail_IsLowercaseStable) keeps the
constant aligned with migration 023's seed so a future rename of m's
account doesn't silently strand the gate. All existing tests pass.
2026-05-07 21:57:20 +02:00
m
7b66c4d035 feat(t-paliad-146): Paliadin PoC — tmux-Claude in-app AI buddy
Phase 0 of the Paliadin design (docs/design-paliadin-2026-05-07.md
§0.5). m-only laptop scope, gated behind PALIADIN_ENABLED=false on
prod. Lifts the goldi/mVoice tmux-Claude pattern (mVoice/server.py:
250-380) into a Go service: long-lived `claude` pane in a tmux
session, prompts in via `tmux send-keys -l`, responses out via a
per-turn file (/tmp/paliadin/{turn_id}.txt) the system prompt
instructs Claude to write.

What landed
-----------
- migration 058_paliadin_poc — paliad.paliadin_turns audit table
  (full prompt + response stored at PoC scope; redaction returns
  at production v1 per design §3.3). RLS: user sees own,
  global_admin sees all.

- internal/services/paliadin.go — the orchestrator. ensurePane()
  finds-or-creates the tagged tmux window, sendToPane sends the
  framed [PALIADIN:turn_id] envelope, pollForResponse reads the
  per-turn file, splitTrailer parses the [paliadin-meta] block
  Claude appends to every reply (used_tools, rows_seen,
  classifier_tag).

- internal/services/paliadin_prompt.go — the system prompt sent
  once to a fresh Claude pane. Defines the response protocol
  (Write-to-file + meta trailer), the action-chip marker syntax,
  the visibility-gate rule (paliad.can_see_project required in
  every project-scoped query), and 9 SQL recipes covering m's
  paliad data + cross-schema youpc case-law lookup.

- internal/handlers/paliadin.go — POST /api/paliadin/turn kicks
  off the work in a goroutine and returns an SSE URL; GET
  /api/paliadin/stream/{id} relays per-turn channel events
  (meta/content/end/error/ping) to EventSource. Routes register
  ONLY when PaliadinService is wired — paliadinSvc nil → no
  handlers exist, prod surface is clean.

- /admin/paliadin dashboard — global_admin-only. Shows total
  turns, last-7-days, median/p90 duration, tool-use rate (the
  load-bearing §0.5.7 metric), abandon rate, classifier
  histogram, daily sparkline, top prompts, recent turn log.
  Powered by PaliadinService.Stats() + ListRecentTurns().

- frontend: paliadin.tsx + client/paliadin.ts (chat panel with
  starter prompts, EventSource consumer, typewriter render of
  one-shot content blob, citation-chip parser, "Stop" + "New
  conversation" buttons, localStorage history); admin-paliadin
  pair (read-only stats dashboard).

- Sidebar: Paliadin entry under Übersicht (ICON_SPARKLE);
  Paliadin Monitor under Admin.

- 36 i18n keys (DE+EN), CSS for chat panel + dashboard.

- main.go: PaliadinService wires only on PALIADIN_ENABLED=true,
  with PALIADIN_TMUX_SESSION + PALIADIN_RESPONSE_DIR overrides.
  Logs visibly so the operator can confirm at boot.

- CLAUDE.md: ANTHROPIC_API_KEY row updated (PoC doesn't need it
  — Claude CLI uses m's subscription; key reserved for future
  production-v1). New rows for the three PALIADIN_* env vars.

Tests
-----
- 7 unit tests on the trailer parser, chip counter, token approx,
  and tmux-input sanitiser. All pass. The trailer parser is
  load-bearing for monitoring; an unobserved parser bug = silent
  dashboard rot.

What's NOT in v1 (stays deferred)
---------------------------------
- The Anthropic API client (production v1, gated on PoC success
  per §0.5.7).
- BYO-AI / OpenAI adapter.
- Per-user rate limiting.
- Multi-replica SSE bus.
- Mascot / avatar SVG.
- Persistent threads (history is browser localStorage only).

How to use locally
------------------
  $ export PALIADIN_ENABLED=true
  $ ./paliad
  # browse /paliadin → type a question → answers stream back
  # /admin/paliadin shows the monitoring dashboard

Migration: 058 (skips fritz's t-147 on 057). Safe on prod
because PALIADIN_ENABLED defaults to false; the table is created
but no routes touch it until the env var flips.
2026-05-07 21:49:33 +02:00
m
1a815979f8 docs(t-paliad-103): warn against ::before block-link overlays
Append a sibling note to the .entity-table contract (t-paliad-099)
explaining that pointer-event overlays for whole-card click break
text selection. Steer future hands at the row-handler pattern from
t-098/099/102/103 instead.
2026-05-04 10:54:09 +02:00
m
2d46a86c50 docs(t-paliad-099): document .entity-table row-click contract
Anchor the convention surfaced by t-paliad-098/099 so the next
hand on .entity-table sees it before adding a new table:

- frontend/src/styles/global.css: contract comment block above the
  default cursor:pointer rule explaining the navigate-or-readonly choice
- .claude/CLAUDE.md: new "Frontend conventions" section pointing at
  the CSS and the row-handler pattern in client/checklists.ts +
  client/projects-detail.ts

No code changes; pure docs.
2026-05-02 11:47:57 +02:00
m
60653a51be docs(claude.md): update Gitea path to m/paliad after transfer
Repo was transferred from mAi/paliad → m/paliad on 2026-04-30; updating
the project CLAUDE.md to reflect. Auto-redirects keep old URLs working.
2026-04-30 16:17:54 +02:00
m
ee1af9d9cf docs: move project status & history out of CLAUDE.md
CLAUDE.md should be AI guidance only. Phase status, shipped milestones,
open follow-ups, and the patHoLo→Paliad rebrand history are project
state — they belong in docs/, not in agent instructions.

Created docs/project-status.md with the full block. CLAUDE.md now points
to it.
2026-04-29 13:54:59 +02:00
m
f0d01a84a4 docs(claude.md): mark Phase I (Notizen) as shipped
Service, handlers, and client module already exist and are wired into
project/deadline/appointment detail pages. The 'pending' note was stale
and risked sending workers to re-implement built code.
2026-04-29 13:53:26 +02:00
m
495e519475 feat(t-paliad-065): firm-agnostic branding via single FIRM_NAME constant
Paliad ships firm-agnostic per CLAUDE.md ("survives firm renames") but
landing copy, email templates, page titles, and form placeholders still
hard-coded "Hogan Lovells" / "HL Patents". Replaces every user-facing
firm reference with a single source of truth: internal/branding.Name on
the server and frontend/src/branding.ts in the bundle, both reading
FIRM_NAME at startup/build time and defaulting to "HLC".

Server: branding package + boot log; auth, invite, admin_users error
strings; courts/offices/models comments; mail templates thread
{{.Firm}} via injected payload default. Files handler keeps the
upstream "HL Patents Style.dotm" path (must match mWorkRepo's blob
name) but renders the user-visible DownloadName from branding.Name.

Frontend: branding.ts read via Bun.build define so process.env.FIRM_NAME
is statically substituted into client bundles (no runtime process
reference); index/login/downloads/kostenrechner/Sidebar/ProjectFormFields
and every i18n.ts string templated against ${FIRM}.

ALLOWED_EMAIL_DOMAINS whitelist intentionally untouched — email
domains and display name rotate independently.

Verified: go build/vet/test clean; bun run build clean; FIRM_NAME=Acme
override produces "Acme" in HTML and JS bundles end-to-end.
2026-04-28 22:44:06 +02:00
m
80518e4dd8 feat(t-paliad-064): migration 025 reminder redesign schema (PR-2)
Schema additions for the new digest-style reminder system:

paliad.users
- reminder_warning_offset_days INT NOT NULL DEFAULT 7, range 1..30
  Per-user customisation of how many days before each deadline the heads-up
  email fires. 7 matches the prior Monday-weekly behaviour.
- escalation_contact_id UUID NULL FK paliad.users(id) ON DELETE SET NULL
  Optional override of the escalation channel for overdue / DRINGEND mail.
  NULL means "fall back to global_admins". UI dropdown deferred to a
  follow-up task per m's 2026-04-28 decision; column ships now to avoid
  a second migration. Self-reference forbidden by CHECK constraint.

paliad.reminder_log
- slot TEXT NULL, slot_date DATE NULL — digest dedup keys.
- reminder_type CHECK widened to admit 'morning_digest' / 'evening_digest'
  alongside the legacy 'overdue' / 'tomorrow' / 'weekly' values.
- Partial UNIQUE INDEX (user_id, slot, slot_date) WHERE slot IS NOT NULL
  enforces "one digest per user per slot per local-date". Legacy rows
  with slot IS NULL are unaffected.

CLAUDE.md updated with a §Phase status note pointing to the design doc
and explaining the deferred Settings-UI dropdown for escalation_contact_id.

Migration is fully additive and idempotent (IF NOT EXISTS / DROP-then-ADD
on named constraints). Down migration reverses the schema cleanly; any
'morning_digest' / 'evening_digest' rows must be deleted before downgrading.
2026-04-28 13:05:22 +02:00
m
83d5973dd6 fix(sidebar): omit changelog badge for anon visitors + clarify CLAUDE.md auth gate (t-paliad-035)
The marketing landing (`/`) renders the same Sidebar as protected pages, so
`initChangelogBadge()` was firing `GET /api/changelog/unseen-count` on every
anon visit and getting 401. Cosmetic noise + wasted round-trip.

Add an `authenticated` prop to Sidebar (defaults to true, no behavior change
on protected pages) and pass `false` from `renderIndex()`. The badge `<a>`
is omitted server-side; the existing `if (!badge) return` guard in
sidebar.ts naturally skips the fetch when the element is absent — no
client change needed.

Also append a clarifying note under the env-var table in .claude/CLAUDE.md:
"work without DB" doesn't mean "ungated for anon". The knowledge-platform
routes (Kostenrechner, Glossar, etc.) are still behind the auth gate; only
`/`, `/login`, `/logout`, and `/assets/*` are public. Misread by the smoke
tester briefer; spelled out now to prevent recurrence.
2026-04-25 23:09:36 +02:00
m
fb401c63c0 docs: update CLAUDE.md — English system language, project hierarchy, team-based visibility 2026-04-20 17:24:18 +02:00
m
11217f7bfa feat: email service — SMTP + deadline reminders + invitations (t-paliad-021)
- internal/services/mail_service.go: SMTP/TLS sender (implicit TLS on 465),
  html/template rendering, branded base layout + content templates, silent
  no-op when SMTP_* unset.
- internal/services/reminder_service.go: hourly scanner for Fristen that are
  overdue / due tomorrow / due within the week (Monday digest). Dedup via
  paliad.reminder_log (24h window).
- internal/services/invite_service.go: POST /api/invite flow with domain
  whitelist, in-memory 10/day/user rate limit, audit row in
  paliad.invitations.
- internal/handlers/invite.go: POST + GET /api/invite handlers.
- Sidebar "Kolleg:in einladen" button + modal on every page.
- migration 016: paliad.reminder_log, paliad.invitations, users.lang column.
- docker-compose: SMTP_* + PALIAD_BASE_URL env vars.
- docs/feature-roadmap.md: documented Supabase auth-SMTP routing as open
  question; current pilot keeps identity mails on Supabase default sender.

Rationale: get Paliad off Supabase's best-effort outbound for the
inbox-facing stuff (reminders, invitations) and move deadline nudges from
passive dashboard to active email. Custom Supabase auth SMTP is blocked on
the shared ydb.youpc.org instance — deferred until Paliad has its own
project or GoTrue webhook relay.
2026-04-20 12:34:38 +02:00
m
d0d4f624a1 docs: Phase J — roadmap rewrite + post-integration status
Rewrite docs/feature-roadmap.md per design-kanzlai-integration.md §5:
- All-in-one positioning: knowledge platform + Aktenverwaltung
- New Phase 0 (Aktenverwaltung Foundation) with shipped A–G items
- "What Paliad Is" replaces "What patholo Is NOT"
- Drop §2.3 UPC Rechtsprechungsübersicht (youpc.org link in Link Hub)
- Phase H (AI Frist-Extraktion) marked deferred
- Mark done items in prioritized backlog with completion dates
- Architecture Notes data-strategy: paliad schema + office-scoped RLS

Refresh .claude/CLAUDE.md:
- Aktenverwaltung + knowledge tools in Purpose
- Env var table incl. DATABASE_URL (Akten*/Fristen/Termine), CALDAV_ENCRYPTION_KEY
- Phase status (A–G shipped, H deferred, I pending)
- Akten naming convention (not "Mandate"/"cases")

Refresh README.md:
- Full feature list (Akten, Fristen, Termine, Dashboard + knowledge tools)
- Migration inventory (001–013), migration tracker note
- Env var table with usage semantics
- Project layout + current project status

Append "Post-Integration Status" section to design-kanzlai-integration.md:
- Per-phase shipment table with merge commits
- Phase H deferral note; Phase I pending note
- Phase J split: docs done here; infra retirement (Dokploy, schema drop,
  repo archive) pending head + m coordination
- Email-gate hardcode flagged for follow-up
2026-04-17 12:10:35 +02:00
m
e760fe94fb chore: rename project patholo → paliad in CLAUDE.md 2026-04-16 12:58:04 +02:00
m
05139a8ea2 feat: initial project setup — mai config, CLAUDE.md
patholo.de — patent knowledge sharing platform for HL colleagues.
Domain purchased, DNS configured, project registered.
2026-04-14 15:57:38 +02:00