Files
paliad/tests/smoke-auth-2026-04-25.md
m 0d0ba6ee1d test(smoke): authenticated production smoke report (t-paliad-034)
Logged-in smoke test against paliad.de as the seeded test admin
(tester@hlc.de). Login flow + every gated route exercised; 17
screenshots and 10 prioritised bugs filed.

Top finding: paliad.can_see_project() still references the renamed-away
projekte/projekt_teams tables, which 500s every RLS-touching endpoint
(/api/projects, /api/deadlines, /api/appointments and the project-detail
page render). Two of today's three new shipments — project tree and
reminder service — cannot be exercised end-to-end as a result. Team
directory works modulo a stray /api/departments?include=members 4xx/5xx.
2026-04-25 23:21:37 +02:00

15 KiB
Raw Permalink Blame History

Paliad Authenticated Smoke Test — 2026-04-25

Target: https://paliad.de Run by: paliad/curie (t-paliad-034) Method: Playwright headless (Chromium 1.59.1, viewport 1280×900) via /mai-tester Auth: logged in as tester@hlc.de (UUID 591183c3-ca3a-4939-acac-5953c1dd08d8, role=admin, office=munich, lang=de) Screenshots: tests/screenshots-auth-2026-04-25/auth-NN-*.png Trigger: follow-up to t-paliad-033 — exercise the gated surface, validate the three features that shipped today.

Headline

Login works. Critical regression: every endpoint that lists Projects/Deadlines/Appointments returns HTTP 500 for the test admin. Knowledge tools, Team directory, Settings, Glossary, Courts and the public landing all render cleanly. Project tree (/api/projects/tree) works in isolation; flat /api/projects does not, breaking /projects, /projects/new (POST), /deadlines, /appointments and the project-detail page render. Reminder service could not be exercised — every prerequisite (create project, list deadlines) is broken end-to-end.

Two new features (Project tree, Team directory) ship in a partial state; reminder service is unverifiable from the UI.

Setup note (resolved before the smoke run)

The first-attempt seed of the tester@hlc.de admin landed in the wrong Postgres (the Supabase MCP I had access to → supa.flexsiebels.de, not Paliad's actual auth DB at youpc 100.99.98.201:11833). Login returned 401 for ~6 minutes while head re-seeded via psql against the correct DB. The original seeded UUID b62cc337-1750-4599-8e42-10764d0b9a43 was deleted from the wrong DB; the working UUID is 591183c3-ca3a-4939-acac-5953c1dd08d8. Worth noting because future smoke tests will hit the same trap: the youpc Postgres MCP is "broken" per ~/.claude/CLAUDE.md, so seeding has to go through psql, not MCP.

Per-URL Results

Core app shell

URL HTTP Console Render Notes
/login (form) 200 clean DE default, EN toggle works, HLC hint visible
POST /api/login 200 clean Cookie set, redirects to /dashboard
/dashboard 200 0 errs (favicon 404 only) ⚠️ Renders; greeting/office correct (Test Tester, München); traffic-light shows 1 Überfällig. Activity log leaks 2/3 raw i18n keys (see Bug 4)
/agenda 200 clean Empty state for "Both / 30 days"; type/range chips work
/changelog 200 clean Renders changelog list, "What's New" header, lime accents
/whatsnew 404 err Brief expected this to work — it doesn't exist; sidebar uses /changelog
/search?q=test 404 err Brief expected this to work — route does not exist
/settings 200 clean Profile tab populated correctly, all 4 tabs (Profile/Notifications/CalDAV/Dezernat) render in the SPA
/settings/caldav 200 clean Direct deep-link works
/settings/notifications, /settings/dezernat 404 ⚠️ Tab deep-links 404 (only caldav is routed); not blocking — in-page tabs work

The three new features

Feature URL Verdict Detail
Project tree (t-paliad-028) /projects (flat and tree) broken Banner "Project management is currently unavailable — please contact an administrator." /api/projects and /api/projects?view=tree both return 500. The new /api/projects/tree endpoint does return 200 with valid JSON, but the page never reaches it because the initial flat-list load fails first. Tree-view dropdown is disabled implicitly by the error banner — no toggle UX possible.
Team directory (t-paliad-029) /team ⚠️ partial Shows Test Tester (admin/Munich) + Matthias (Counsel Knowledge Lawyer / Düsseldorf, real prod data). "By office" / "By department" toggle works; office filter pills work (Munich filter narrows to 1/2 correctly); search by name works (Tester → 1/2). But: GET /api/departments?include=members returns 404/500 silently — page still renders because the data falls back to /api/users. The fallback works; the dept include endpoint is bogus.
Reminder service (t-paliad-032) (UI prerequisite) unverifiable Cannot create a fresh deadline: POST /api/projects returns 500, so no project to attach a deadline to. The existing prod project (Mandant vs Gegner) is invisible in the project-detail page (Bug 2), so its deadline form is unreachable. SMTP / email side untested.

Knowledge platform (auth-gated, all green)

URL HTTP Console Visual
/tools/fristenrechner 200 clean Patent Deadline Calculator renders, UPC/German/EPO selectors visible
/tools/kostenrechner 200 clean Cost Calculator renders with default 1M EUR, totals computed live (51,924.92 €)
/tools/gebuehrentabellen 200 clean
/checklists 200 clean
/glossary 200 clean 89/89 terms; DE→EN toggle changes title to Patentglossar correctly
/courts 200 clean 41/41 courts; UPC/Germany/EPA/National + 13 country filters
/links 200 clean
/downloads 200 clean

Aktenverwaltung core (broken)

URL HTTP Failed XHRs Notes
/projects 200 (page) /api/projects → 500 "Project management is currently unavailable"
/projects?view=tree 200 (page) /api/projects?view=tree → 500 Same banner; view=tree query param has no SSR effect
/projects/new 200 (page) POST /api/projects → 500 Form renders; submit displays internal error inline; no project created
/projects/{id} 200 (page) n/a "Project not found or no access." But GET /api/projects/{id} returns 200 — page-render visibility check is inconsistent with the API
/projects/{id}/deadlines 200 (page) n/a Same "not found or no access" — same root cause
/deadlines 200 (page) /api/deadlines?status=pending, /api/deadlines/summary, /api/projects all 500 "Deadline management currently unavailable"
/appointments 200 (page) /api/appointments?, /api/appointments/summary, /api/projects all 500 "Appointments unavailable — please contact your administrator."

DE/EN toggle smoke

Page DE EN Verdict
/dashboard "Guten Tag, Test Tester" "My matters / Sign Out / Internal use only"
/projects "Projekte" sidebar / "Akten" "Projects / Internal use only" (chrome)
/glossary "Patentglossar" / "Patentglossar — Paliad" title "Patent Glossary"

Bug-hunt findings (top 10 by severity)

🔴 Bug 1 — /api/projects (GET list, POST create) returns 500

Severity: showstopper for Aktenverwaltung. Reproduction: curl https://paliad.de/api/projects with the test admin's session cookie returns {"error":"internal error"}. Same pattern for POST /api/projects (project creation) and for /api/deadlines, /api/appointments, /api/deadlines/summary, /api/appointments/summary. Likely root cause: see Bug 3.

🔴 Bug 2 — /projects/{id} SSR shows "not found" while /api/projects/{id} returns 200

Severity: breaks every project-detail navigation. Repro: curl https://paliad.de/api/projects/b0b39a87-c67e-4fa2-8935-4511ccce80dc → 200 with full payload; visiting https://paliad.de/projects/b0b39a87-c67e-4fa2-8935-4511ccce80dc in the browser → page text "Project not found or no access." The page-render handler (handleProjectsDetailPage, registered for 9 sub-paths in internal/handlers/handlers.go:219-227) uses a different visibility check than the /api/projects/{id} handler. Almost certainly downstream of Bug 3.

🔴 Bug 3 — paliad.can_see_project() references renamed-away tables

Severity: root cause for Bugs 1 + 2 + the broken /deadlines and /appointments. The DB function still queries paliad.projekte and paliad.projekt_teams (German names dropped during the rename to English):

-- excerpt from pg_get_functiondef('paliad.can_see_project')
FROM paliad.projekte  target
JOIN paliad.projekt_teams pt
  ON pt.user_id = auth.uid()
 AND pt.projekt_id = ANY(string_to_array(target.path, '.')::uuid[])
WHERE target.id = _projekt_id

These tables no longer exist (SELECT EXISTS(... 'projekte') AS has_projektef). Calling the function fails: ERROR: relation "paliad.projekte" does not exist. The function is called by the RLS policies on paliad.projects (projekte_select USING (paliad.can_see_project(id))), so any code path that exercises RLS — page-render handlers, anything not running as supabase_admin/postgres — errors out. The Go tree handler probably works because it uses the service-side visibilityPredicate SQL embedded in its own query and runs as the connection's elevated role, bypassing RLS.

Fix: update the function body to reference paliad.projects and paliad.project_teams. There's likely a missing migration that renamed the tables but skipped the functions/policies.

CREATE OR REPLACE FUNCTION paliad.can_see_project(_projekt_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.role = '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 = _projekt_id);
$$;

The grep also turned up old-name fkey constraints (projekte_*_fkey, akten_events_projekt_id_fkey, dokumente_projekt_id_fkey, fristen_projekt_id_fkey, notizen_projekt_id_fkey, parteien_projekt_id_fkey, projekt_teams_projekt_id_fkey, termine_projekt_id_fkey) — cosmetic, but they suggest the rename migration was a ALTER TABLE ... RENAME TO only and never touched dependent objects. Worth a sweep.

🟠 Bug 4 — Dashboard activity log leaks raw i18n keys

Severity: cosmetic, but it's on the landing page — worst place for it. Visible strings on /dashboard "Letzte Aktivität":

  • Matthias dashboard.action.notiz_created 000001.000001 Notiz hinzugefügt
  • Matthias dashboard.action.frist_created 000001.000001 Frist angelegt
  • Matthias legte Akte an 000001.000001 Akte created ← the third entry mixes translated DE ("legte Akte an") with raw EN ("Akte created") — likely two different fallback paths.

The keys dashboard.action.notiz_created and dashboard.action.frist_created are not in the i18n bundle for DE (and presumably not EN either). The third entry's "Akte created" suffix looks like a default English title from the audit-event row that didn't go through translation. Three separate bugs in the same widget.

🟠 Bug 5 — Untranslated dropdown labels in /deadlines and /appointments

Severity: cosmetic. The Matter filter on /deadlines shows fristen.filter.project.all as its first option text; /appointments shows termine.filter.project.all. Both are raw i18n keys. (The pages themselves are also broken — Bug 1 — but the label leak is independent.)

🟠 Bug 6 — /api/departments?include=members returns 404 / 500 inconsistently

Severity: low (page renders via fallback), but worth tightening. First load on /team produced a 500; later fetches from the page return 404. Either the route shouldn't be hit at all (front-end should stop firing it) or it should be implemented. Both observed failures are silently swallowed by the team page — visible only in the console.

🟠 Bug 7 — /whatsnew and /search return 404

Severity: medium. The brief expected both routes to work; reality is /changelog (named "What's New" in the UI) and no /search page at all. Either:

  • the brief is stale (these routes were never added) — fix the brief,
  • or the routes were dropped in a rename — restore or redirect.

The sidebar correctly links to /changelog; users who type /whatsnew from memory will hit a bare "404 page not found" with no chrome at all (same for /search).

Severity: low. Tabs visible in the UI: Profile / Notifications / CalDAV / Dezernat. Direct URL /settings/caldav → 200 . /settings/notifications, /settings/dezernat → 404. The in-page click navigation works for all four — only the deep-link form is inconsistent.

🟡 Bug 9 — 404 pages have no chrome

Severity: polish. /whatsnew, /search, /settings/notifications all render bare 404 page not found text — no sidebar, no header, no link back. Existing logged-in users hitting a typo get a jarring full-page break. /projects/{nonexistent-uuid} correctly renders the chrome + a friendly "not found" card; the 404 handler should match that style.

🟡 Bug 10 — Login form preserves session on failure

Severity: very minor. After a failed login attempt, the prior 401 console error stays attached to the session and is later replayed when console history is requested with all=true. Functional impact: zero. Surface this only if you're chasing a clean console for marketing screenshots.


Acceptance check

  • Login successful, cookie persisted, dashboard reached
  • ⚠️ Three new features exercised — Team directory , Project tree (page broken), Reminder service (unverifiable, no path to create a deadline)
  • Report file written
  • MCP mcp__mai__update_task(id="t-paliad-034", status="done") will be called next
  • Commit pending

Cleanup

Per brief: test admin (tester@hlc.de, UUID 591183c3-ca3a-4939-acac-5953c1dd08d8) left intact in youpc Postgres; password unchanged; no rows deleted.

Side-effect to flag: while debugging the seeding mismatch, I overwrote auth.users.encrypted_password for the decoy UUID b62cc337-1750-4599-8e42-10764d0b9a43 in supa.flexsiebels.de Supabase (twice, with different bcrypt hashes for the same brief password). That DB is not Paliad's auth DB so there is no production impact, but if anything else points at that decoy it's now in an unspecified state. Head deleted the decoy UUID before reseeding — should be fully gone.

Recommendation

  1. Ship a hotfix migration that rewrites paliad.can_see_project to use the renamed tables (Bug 3). Without it the entire admin/lead surface for projects/deadlines/appointments is unusable. Real production users may also be affected — needs verification with a non-admin login.
  2. Audit the rename migration for other stale references: function bodies, fkey constraint names (cosmetic), trigger functions (projekte_rewrite_subtree, projekte_sync_path are still old-named — they sit on paliad.projects so they probably work via the trigger binding, but worth a sweep).
  3. Reconcile the brief: /whatsnew and /search either need to exist or stop being expected.
  4. Fix the activity-log i18n keys before the next user-facing demo (Bug 4).
  5. Wire the 404 handler into the chrome (Bug 9) — small change, big polish gain.