fix: use service-role client for processing_history writes (#289)
appendProcessingHistory was accepting a caller-provided SupabaseClient, but processing_history has no INSERT RLS policy (by design — audit trail writes should not be user-controllable). Extension handlers pass the authenticated user client (ctx.supabase), which caused silent RLS violations caught by try-catch — the audit trail was wired up but empty. Switch to creating a service-role client internally, matching the existing event_log pattern (event-log-handler.ts uses createServiceClientNoCookies). Remove the supabase parameter from both appendProcessingHistory and appendProcessingHistoryBatch. Update all 5 call sites in invoice-inbox and inbox-smart-match. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
aec14f0ca7
commit
dd920355b3
@@ -68,7 +68,7 @@ export async function processInboxItemMatch(
|
||||
// Append DeterministicMatch event — records that the narrowing ran
|
||||
let deterministicEventId: string
|
||||
try {
|
||||
deterministicEventId = await appendProcessingHistory(ctx.supabase, {
|
||||
deterministicEventId = await appendProcessingHistory({
|
||||
companyId: ctx.companyId,
|
||||
correlationId,
|
||||
aggregateType: 'MatchProposal',
|
||||
@@ -121,7 +121,7 @@ export async function processInboxItemMatch(
|
||||
|
||||
// Record the LLM attempt in processing_history
|
||||
try {
|
||||
await appendProcessingHistory(ctx.supabase, {
|
||||
await appendProcessingHistory({
|
||||
companyId: ctx.companyId,
|
||||
correlationId,
|
||||
causationId: deterministicEventId,
|
||||
|
||||
@@ -68,7 +68,7 @@ async function uploadAndClassify(
|
||||
|
||||
// Audit: DocumentIngested
|
||||
try {
|
||||
await appendProcessingHistory(supabase, {
|
||||
await appendProcessingHistory({
|
||||
companyId,
|
||||
correlationId,
|
||||
aggregateType: 'Document',
|
||||
@@ -102,7 +102,7 @@ async function uploadAndClassify(
|
||||
|
||||
// Audit: DocumentExtractionAttempted (fires whether classification succeeded or failed)
|
||||
try {
|
||||
await appendProcessingHistory(supabase, {
|
||||
await appendProcessingHistory({
|
||||
companyId,
|
||||
correlationId,
|
||||
aggregateType: 'Document',
|
||||
@@ -190,7 +190,7 @@ async function uploadAndClassify(
|
||||
// Audit: DocumentClassified (only when classification succeeded)
|
||||
if (!classificationError && classificationResult) {
|
||||
try {
|
||||
await appendProcessingHistory(supabase, {
|
||||
await appendProcessingHistory({
|
||||
companyId,
|
||||
correlationId,
|
||||
aggregateType: 'Document',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Processing history (behandlingshistorik) — append helper.
|
||||
*
|
||||
* Appends events to the processing_history table within the caller's
|
||||
* database transaction. Throws on failure so that the table writes
|
||||
* and the audit trail are atomically consistent.
|
||||
* Uses a service-role client internally (no INSERT RLS policy on
|
||||
* processing_history — matching the event_log pattern). Company scoping
|
||||
* is enforced by companyId in the event payload, not by RLS.
|
||||
* Throws on failure.
|
||||
*
|
||||
* PII BOUNDARY: payload MUST contain pseudonymous IDs only (user UUIDs,
|
||||
* company UUIDs, counterparty IDs). Never names, emails, personnummer,
|
||||
@@ -13,11 +14,11 @@
|
||||
* reference, which is the required behavior per v0.2 §10.
|
||||
*/
|
||||
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import type {
|
||||
ProcessingHistoryAggregateType,
|
||||
ProcessingHistoryActor,
|
||||
} from '@/types'
|
||||
import { createServiceClient } from '@/lib/supabase/server'
|
||||
import { z } from 'zod'
|
||||
|
||||
// ── PII validator ───────────────────────────────────────────────
|
||||
@@ -90,16 +91,15 @@ export interface AppendEventInput {
|
||||
// ── Append functions ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Append a single event to processing_history within the caller's transaction.
|
||||
* Append a single event to processing_history.
|
||||
*
|
||||
* Uses the provided SupabaseClient (which should be the same client used for
|
||||
* table writes in the command handler). Throws on failure so that both the
|
||||
* table writes and the audit trail roll back together.
|
||||
* Uses a service-role client internally (bypasses RLS) since processing_history
|
||||
* has no INSERT policy — matching the event_log pattern. Company scoping is
|
||||
* enforced by the companyId in the event payload, not by RLS.
|
||||
*
|
||||
* Returns the generated event_id (pre-generated client-side for causation chaining).
|
||||
*/
|
||||
export async function appendProcessingHistory(
|
||||
supabase: SupabaseClient,
|
||||
input: AppendEventInput
|
||||
): Promise<string> {
|
||||
// Validate payload + actor.label contain no PII
|
||||
@@ -107,6 +107,7 @@ export async function appendProcessingHistory(
|
||||
assertActorPiiSafe(input.actor)
|
||||
|
||||
const eventId = crypto.randomUUID()
|
||||
const supabase = createServiceClient()
|
||||
|
||||
const { error } = await supabase
|
||||
.from('processing_history')
|
||||
@@ -135,13 +136,12 @@ export async function appendProcessingHistory(
|
||||
}
|
||||
|
||||
/**
|
||||
* Append multiple events atomically within the caller's transaction.
|
||||
* Append multiple events atomically (single INSERT).
|
||||
* Used for batch operations (e.g., migration commits, multi-event command handlers).
|
||||
*
|
||||
* Returns array of generated event_ids in input order.
|
||||
*/
|
||||
export async function appendProcessingHistoryBatch(
|
||||
supabase: SupabaseClient,
|
||||
inputs: AppendEventInput[]
|
||||
): Promise<string[]> {
|
||||
if (inputs.length === 0) return []
|
||||
@@ -169,6 +169,8 @@ export async function appendProcessingHistoryBatch(
|
||||
occurred_at: input.occurredAt.toISOString(),
|
||||
}))
|
||||
|
||||
const supabase = createServiceClient()
|
||||
|
||||
const { error } = await supabase
|
||||
.from('processing_history')
|
||||
.insert(rows)
|
||||
|
||||
Reference in New Issue
Block a user