fix(salary): stop RLS from failing vab/parental absence registration (#1568)
Migration 20260517135000 rewrote the franvaro-specifikationsnummer trigger functions to insert audit rows into salary_absence_franvaro_audit, a table with RLS enabled and zero policies, while leaving the functions SECURITY INVOKER (its comment claimed implicit SECURITY DEFINER, which is false in Postgres). Every vab/parental insert from role authenticated (dashboard absence POST, web /pending approval, in-app Assistenten chat) then failed with 42501, surfaced as a generic 500, and left no diagnosable trace. - New migration 20260813120000: ALTER both trigger functions to SECURITY DEFINER with search_path pinned to public, pg_temp. No RLS policy is added on the audit table: trigger/service-only writes stay the design intent. - mapInsertError: 42501 now maps to the new bilingual DB_PERMISSION_DENIED code instead of INTERNAL_ERROR, and 23514 is split so only the 24h-cap trigger's 'Total tid' message becomes ABSENCE_HOURS_CONFLICT; other CHECK violations map to VALIDATION_ERROR. - commitRegisterAbsence/commitDeleteAbsence: log the underlying PG details and persist the sanitized structured code in result_data.error_code so the next failure is traceable from the op row. - Dashboard absence route: only ABSENCE_HOURS_CONFLICT passes details.message through to the client; every other code shows the registry Swedish message instead of raw Postgres text. - New pg-real regression test locks the authenticated-role parental/vab insert path, the shared per-month specnummer sequence, the audit rows, and idempotent upsert retries. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
ebeaeec80e
commit
c4adc8eb7d
@@ -4982,9 +4982,23 @@ async function commitRegisterAbsence(
|
||||
includeWeekends: (params.include_weekends as boolean | undefined) ?? false,
|
||||
})
|
||||
if (!result.ok) {
|
||||
// The user-facing message is generic Swedish; without this log the
|
||||
// underlying PG error (e.g. the franvaro audit-trigger RLS denial that
|
||||
// caused five untraceable 500s, feedback 2026-08-13) leaves no trace.
|
||||
// pgDetails, not details: `details` is the logger record's own field
|
||||
// for non-object args and would be swallowed by the pretty emitter.
|
||||
createLogger('commit/register_absence').error('register_absence commit failed', {
|
||||
code: result.code,
|
||||
pgDetails: result.details,
|
||||
employeeId,
|
||||
absenceType,
|
||||
from,
|
||||
to,
|
||||
})
|
||||
const entry = getErrorEntry(result.code)
|
||||
return {
|
||||
error: entry?.message_sv ?? `Kunde inte registrera frånvaron: ${result.code}`,
|
||||
errorCode: result.code,
|
||||
status: entry?.httpStatus ?? 500,
|
||||
}
|
||||
}
|
||||
@@ -5081,9 +5095,19 @@ async function commitDeleteAbsence(
|
||||
absenceType: (params.absence_type as string | undefined) || undefined,
|
||||
})
|
||||
if (!result.ok) {
|
||||
// Same diagnosability treatment as commitRegisterAbsence: keep the PG
|
||||
// error in the logs and the registry code on the op row.
|
||||
createLogger('commit/delete_absence').error('delete_absence commit failed', {
|
||||
code: result.code,
|
||||
pgDetails: result.details,
|
||||
employeeId,
|
||||
from,
|
||||
to,
|
||||
})
|
||||
const entry = getErrorEntry(result.code)
|
||||
return {
|
||||
error: entry?.message_sv ?? `Kunde inte ta bort frånvaron: ${result.code}`,
|
||||
errorCode: result.code,
|
||||
status: entry?.httpStatus ?? 500,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user