fix(inbox): never extract the receiving company as its own supplier (#2080)
* fix(inbox): never extract the receiving company as its own supplier The model sometimes reads the Kund/Kunduppgifter block of bank agreements and similar documents as the issuer, so the inbox offered to create the user's own company as a leverantor. Two layers: a supplier-direction rule in the extraction prompt (issuer, never the customer/recipient block), and a deterministic post-extraction guard that nulls the supplier block when its org number, derived VAT number, or exact name equals the receiving company's own. All AI extraction paths (upload, deferred, retry, attach-document, document-extraction, MCP) pass the company identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hqm9QgdyNAFaWiz6Ww7pgb * fix(inbox): keep the own-company guard alive for photographed documents Skeptic findings on PR #2080: normalizeImageForExtraction rebuilt the input without ownCompany, so the guard never fired for HEIC/oversized phone photos (the motivating case). Spread the original input instead. Also let a provably different extracted org number outvote a name coincidence, and accept 12-digit personnummer-form own org numbers (19/20 century prefix, enskild firma) alongside the 16-prefixed organisation form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hqm9QgdyNAFaWiz6Ww7pgb * docs(compliance): add RoPA entry for the own-company-identity lookup Compliance swarm (GDPR Art. 30): fetchOwnCompanyIdentity reads companies.name and org_number on every extraction; record the processing activity (transient in-memory comparison, never sent to the model). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hqm9QgdyNAFaWiz6Ww7pgb * fix(inbox): log own-company identity lookup failures instead of failing silent Compliance swarm (SOC 2 CC7.2): the bare catch in fetchOwnCompanyIdentity made a persistently broken lookup (RLS misconfig, DB outage) disable the guard invisibly. Also pin the VAT-number match across prefixed and bare digit forms in the test matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hqm9QgdyNAFaWiz6Ww7pgb * fix(inbox): surface maybeSingle query errors in the own-company lookup CodeRabbit: maybeSingle() reports RLS/query failures in error without throwing, so the failure log added for SOC 2 CC7.2 never fired for exactly those cases. Throw the reported error into the existing catch (still fail open to nulls). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hqm9QgdyNAFaWiz6Ww7pgb --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
fc54e79e08
commit
84c8e1ce59
@@ -10,6 +10,7 @@ vi.mock('@/lib/supabase/server', () => ({
|
||||
const extractMock = vi.fn()
|
||||
vi.mock('@/extensions/general/invoice-inbox/lib/extract-invoice-fields', () => ({
|
||||
extractInvoiceFields: (...args: unknown[]) => extractMock(...args),
|
||||
fetchOwnCompanyIdentity: vi.fn().mockResolvedValue({ orgNumber: null, name: null }),
|
||||
}))
|
||||
|
||||
const hasCapabilityMock = vi.fn()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Extension } from '@/lib/extensions/types'
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import { extractInvoiceFields } from '@/extensions/general/invoice-inbox/lib/extract-invoice-fields'
|
||||
import { extractInvoiceFields, fetchOwnCompanyIdentity } from '@/extensions/general/invoice-inbox/lib/extract-invoice-fields'
|
||||
import { getAiStatus } from '@/lib/ai'
|
||||
import { hasCapability } from '@/lib/entitlements/has-capability'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
@@ -176,6 +176,7 @@ async function extractAndPersist(
|
||||
buffer,
|
||||
mimeType,
|
||||
fileName: (document.file_name as string) || 'document',
|
||||
ownCompany: await fetchOwnCompanyIdentity(supabase, companyId),
|
||||
})
|
||||
// extractInvoiceFields returns an "empty" result on failure rather than
|
||||
// throwing. `skipped` means no model call was made (and why); a null
|
||||
|
||||
Reference in New Issue
Block a user