Layering cleanup found while verifying tesks-ui against the route|action → service → DAL rule (CLAUDE.md). The rest of the app is exemplary — lib/sql/* is a pure DAL (imports only lib/db), all writes go through lib/api.ts, no action touches the DB, and components are presentational.
The one deviation: lib/workspace.ts is the only file outside lib/sql/ that imports the raw db pool. It runs inline Kysely queries — ensureLinked (unlinked-membership probe) and getWorkspaces (staff tenants / viewer memberships / public tenants) — while also doing RBAC role resolution, cookie handling, access guards, and a slugify util. That breaks the "only the DAL owns db" invariant and mixes a data-access layer into a service file.
Fix (bounded): extract the four queries into a lib/sql/tenants.ts DAL module (query functions + row types only); leave lib/workspace.ts as a pure resolver/service over the DAL (role mapping, cookies, requireWorkspaceWrite/Manage, guards). No behaviour change — same selects, same mapping.
Not touching the read-side "pages call lib/sql directly" pattern — that's intentional/idiomatic for Server Components; adding pass-through services would be needless indirection.