cce0de5704
* feat(mcp): already-explained voucher guard at stage and commit for match_batch_allocate The dashboard match-batch route refused BATCH_TX_POSSIBLE_DUPLICATE when posted, unlinked vouchers already summed to the bank row (PR #2300), but the MCP door (gnubok_match_batch_allocate staging + commitMatchBatchAllocate) called the RPC with no guard, so an agent could book a Bankgirot aggregate a second time. The detector existed once; the guard lived in one door. One shared decision helper, lib/invoices/already-explained-guard.ts, now sits on top of the existing detectors (no fork) and is called by the dashboard route, the MCP staging tools and the commit executors: - gnubok_match_batch_allocate refuses to stage, coded BATCH_TX_POSSIBLE_DUPLICATE, naming the vouchers, the reconcile_match / link_transaction_to_journal_entry call that resolves the row, and the exact force + expected_journal_entry_ids binding. - commitMatchBatchAllocate runs the same guard before the RPC and re-validates a staged force binding against the set detected at commit, so a stale approval cannot book a duplicate; 409 auto-rejects with the vouchers in result_data. - force + expected_journal_entry_ids on the tool mirror MatchBatchSchema; an honoured override stages with a compliance_warning and, after the booking succeeds, writes BankTransactionDuplicateDismissed to behandlingshistorik (dashboard route included; it only logged before). - gnubok_match_transaction_to_invoice and commitMatchTransactionInvoice get the dashboard's 1:1 soft-duplicate guard (MATCH_INVOICE_POSSIBLE_DUPLICATE / MATCH_INVOICE_FORCE_CANDIDATE_MISMATCH) with force + expected_journal_entry_id; at commit it runs before the storno. - Registry: both duplicate codes gain retryable: false and a remediation. Catalog payload held under the 60K ceiling by trimming the two tools' own descriptions (59 988 measured, ledger entry in payload-size.bench.test.ts). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SaJfqNi4VmsG8FMKq99G6 * docs(decisions): record the 2026-09-06 ten-issue batch's first-principles choices Carries the DECISIONS.md lines for PRs #2337 #2339 #2340 #2341 #2342 #2343 #2344 #2345 #2346 #2347 in one place so the ten branches do not conflict on this file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SaJfqNi4VmsG8FMKq99G6 * fix(mcp): refuse an unverifiable forced override, surface a failed duplicate check, validate the binding (#2294 review) Review round on PR #2346 (CodeRabbit + compliance): - guardAlreadyExplained returned 'clear' when the detector threw even with force=true, so a forced 1:N override could book without re-validating expected_journal_entry_ids and left no behandlingshistorik record. It now returns a distinct 'unverifiable' outcome under force (mirrors guardDuplicatePaymentVoucher); the dashboard route, the MCP staging tool and the commit executor all refuse it with the new registry code BATCH_TX_EXPLAINED_CHECK_FAILED (409, retryable, remediation). Regression tests on every caller. - A detector failure without force still fails open at stage time, but no longer silently: the tools track onDetectError and stage a complianceNote, so preview_data.compliance_warning is set on both match_batch_allocate (GenericPreview renders it) and match_transaction_invoice (MatchTransactionInvoicePreview now renders data.compliance_warning through AttnLine). - expected_journal_entry_ids / expected_journal_entry_id are validated at the MCP boundary (array of 1 to 10 non-empty strings / non-empty string) and refused with VALIDATION_ERROR instead of being silently filtered. No schema description text added: catalog payload unchanged. - RoPA: .compliance/ropa.yaml gains bookkeeping.duplicate_dismissal_history for the BankTransactionDuplicateDismissed record (Art. 6(1)(c), BFNAR 2013:2 p. 9.16, retention per BFL 7 kap, stored in processing_history). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
900 lines
38 KiB
YAML
900 lines
38 KiB
YAML
# Record of Processing Activities (GDPR Art. 30)
|
|
#
|
|
# Each entry documents one processing activity. Field shape follows EDPB
|
|
# Guidelines 9/2022 §3 (controller-side RoPA): purposes, lawful basis,
|
|
# data subject + data category, recipients, transfer mechanism, retention,
|
|
# security measures. Add an entry whenever a new flow ships that touches
|
|
# personal data: onboarding, integrations, support, or regulator filings.
|
|
|
|
processing_activities:
|
|
|
|
- id: invoices.delivery_history
|
|
name: Leveranshistorik för kundfakturor
|
|
purpose: >-
|
|
Dokumentera att en kundfaktura skickades, när leveransförsöket skedde,
|
|
vilken mottagardomän som användes och vilken exakt PDF som skickades.
|
|
Dashboardens listvy visar endast maskerade mottagardomäner och
|
|
driftmetadata. Fullständigt meddelandeinnehåll stannar på serversidan.
|
|
lawful_basis: art_6_1_b_and_c
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase_and_resend
|
|
data_subjects:
|
|
- customer_contact
|
|
- company_member
|
|
data_categories:
|
|
- user.contact.email
|
|
- user.communication_content
|
|
- user.activity_timestamp
|
|
- user.document
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
- name: Resend
|
|
country: US
|
|
role: processor
|
|
international_transfers:
|
|
applicable: true
|
|
mechanism: scc_2021_c2p
|
|
note: Resend processes the outbound delivery under SCC Module 2.
|
|
retention:
|
|
duration: through_seventh_calendar_year_after_fiscal_year_end
|
|
basis: bfl_7_kap_and_gdpr_storage_limitation
|
|
stored_in:
|
|
- invoice_deliveries
|
|
- document_attachments
|
|
security_measures:
|
|
- active_company_route_context
|
|
- rls_company_scoped
|
|
- masked_recipient_domains_in_list_api
|
|
- no_message_content_in_browser_list_api
|
|
- exact_sent_pdf_worm_protection
|
|
- metadata_only_immutable_audit_log
|
|
- daily_post_retention_pii_redaction
|
|
- signed_provider_delivery_webhook_only
|
|
- masked_recipient_addresses_in_provider_reason_text
|
|
|
|
- id: customer.private_identity
|
|
name: Personnummer för privatkund
|
|
purpose: >-
|
|
Identifiera en privatkund när företagets avtal, fakturering eller
|
|
kundadministration kräver en entydig identitet. Personnumret får endast
|
|
sparas på kundtypen privatkund. Samtliga listnings- och exportytor i API
|
|
och UI exponerar det endast maskerat (fyra sista siffrorna). Undantaget är
|
|
en enskild uppslagsyta (GET /api/customers/{id}/personal-number) som
|
|
returnerar hela värdet för en kund i taget, kräver skrivroll och loggas
|
|
med aktör men aldrig med värdet, så att den som registrerat ett
|
|
personnummer kan kontrollera vad som faktiskt sparats.
|
|
lawful_basis: art_6_1_b
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- customer
|
|
data_categories:
|
|
- user.government_id
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor.
|
|
retention:
|
|
duration: customer_relationship_or_7y_if_accounting_record
|
|
basis: contract_and_bfl_7_kap
|
|
stored_in:
|
|
- customers.personal_number
|
|
- retained_invoice_documents_when_required
|
|
security_measures:
|
|
- aes_256_gcm_field_encryption
|
|
- masked_api_and_ui_output_last4_only
|
|
- write_role_required
|
|
- rls_company_scoped
|
|
# Replaces the previous `no_full_value_read_endpoint`. A single explicit
|
|
# drill-in now exists (GET /api/customers/{id}/personal-number): the
|
|
# value was otherwise write-only, so a user could store a personnummer
|
|
# and never verify what had been stored. Every listing surface still
|
|
# masks; the drill-in requires the write role, returns one customer at a
|
|
# time, and logs actor + customer id without the value.
|
|
- full_value_read_only_via_single_customer_drill_in
|
|
- full_value_read_requires_write_role
|
|
- full_value_read_logged_with_actor_never_value
|
|
|
|
- id: agi.submit
|
|
name: AGI inlämning till Skatteverket
|
|
purpose: >-
|
|
Lämna in lagstadgad arbetsgivardeklaration på individnivå (AGI) till
|
|
Skatteverket varje månad (Skatteförfarandelagen 26 kap.). Innefattar
|
|
huvuduppgift (HU) och individuppgifter (IU) per anställd. Skatteverkets
|
|
kvittens sparas i response_data inklusive signeradAv: personnummer för
|
|
den som BankID-signerade inlämningen. Ändamålet är bevis för vem som
|
|
juridiskt undertecknade deklarationen samt behandlingshistorik; BFL
|
|
5 kap. 6 § och BFNAR 2013:2 kap. 8 kräver att räkenskapsinformationen
|
|
bevarar den faktiska ingivaren, medan själva inlämningen styrs av SFL.
|
|
lawful_basis: art_6_1_c # legal obligation
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na # software supplier; Skatteverket is recipient, not processor
|
|
data_subjects:
|
|
- employee
|
|
- company_signatory # firmatecknare/deklarationsombud (BankID-signerare av kvittensen)
|
|
data_categories:
|
|
- user.government_id # personnummer (anställda i IU; signerarens i response_data.signeradAv)
|
|
- user.name
|
|
- user.financial.employment # gross salary, tax withheld, benefits
|
|
- user.financial.tax # avgifter, sjuklönekostnad
|
|
recipients:
|
|
- name: Skatteverket
|
|
country: SE
|
|
role: legal_recipient
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: >-
|
|
Sweden-to-Sweden processor-to-public-authority flow. Adequacy decision
|
|
not applicable; no third-country transfer.
|
|
retention:
|
|
duration: 7y
|
|
basis: bfl_7_kap # BFL 7 kap. 2 §: räkenskapsinformation
|
|
stored_in:
|
|
- agi_declarations.xml_content
|
|
- agi_declarations.individuppgifter
|
|
- agi_declarations.response_data # SKV kvittens incl. signeradAv (signerarens pnr); access via company-membership RLS
|
|
- skatteverket_api_audit_log
|
|
security_measures:
|
|
- encryption_at_rest_supabase
|
|
- rls_company_scoped
|
|
- tls_1_3_to_skatteverket
|
|
- bankid_signing_required_for_filing
|
|
- immutable_audit_log
|
|
- personnummer_at_rest_encryption
|
|
|
|
- id: agi.kontrollera.preflight
|
|
name: AGI pre-flight kontrollera (HU/IU)
|
|
purpose: >-
|
|
Skicka enskild HU eller IU som JSON till Skatteverkets kontrollera-
|
|
ändpunkt (v1.7 §7/§8) för dry-run-validering av felmeddelanden och
|
|
STOPP-/AVVISANDE-status innan ett fullständigt underlag laddas upp.
|
|
Skatteverket lagrar inget från denna kontroll; svaret driver vår UI
|
|
som låter användaren rätta felaktigheter i lönekörningen lokalt.
|
|
lawful_basis: art_6_1_c # legal obligation (ancillary to AGI filing)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na
|
|
data_subjects:
|
|
- employee
|
|
data_categories:
|
|
- user.government_id # personnummer (BetalningsmottagarId)
|
|
- user.financial.employment # gross salary, tax withheld, benefits
|
|
recipients:
|
|
- name: Skatteverket
|
|
country: SE
|
|
role: legal_recipient
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: Sweden-to-Sweden flow; no third-country transfer.
|
|
retention:
|
|
# Skatteverket retains nothing for kontrollera calls. Our outbound
|
|
# audit row in skatteverket_api_audit_log records who-called-what-when
|
|
# but NOT the payload body, minimising at-rest footprint.
|
|
duration: 7y
|
|
basis: bfl_7_kap
|
|
stored_in:
|
|
- skatteverket_api_audit_log
|
|
security_measures:
|
|
- strict_zod_allow_list_input_validation
|
|
- request_size_cap_64kb
|
|
- rbac_company_member_role_check
|
|
- rls_company_scoped
|
|
- tls_1_3_to_skatteverket
|
|
- immutable_audit_log
|
|
- no_payload_body_persisted
|
|
|
|
- id: moms.declaration
|
|
name: Momsdeklaration till Skatteverket
|
|
purpose: >-
|
|
Lämna in lagstadgad momsdeklaration (utkast, lås och signering) till
|
|
Skatteverkets momsdeklaration-API (Mervärdesskattelagen 17 kap. 24 §).
|
|
lawful_basis: art_6_1_c
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na
|
|
data_subjects:
|
|
- business_owner
|
|
data_categories:
|
|
- user.government_id # orgnummer (juridiska personer) eller pnr
|
|
- user.financial.tax # rutor 05-62
|
|
recipients:
|
|
- name: Skatteverket
|
|
country: SE
|
|
role: legal_recipient
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: Sweden-to-Sweden flow; no third-country transfer.
|
|
retention:
|
|
duration: 7y
|
|
basis: bfl_7_kap
|
|
stored_in:
|
|
- extension_data (utkast metadata)
|
|
- skatteverket_api_audit_log
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- tls_1_3_to_skatteverket
|
|
- bankid_signing_required_for_filing
|
|
- immutable_audit_log
|
|
|
|
- id: moms.declaration_status_read
|
|
name: Avläsning av inlämnad momsdeklaration och beslut från Skatteverket
|
|
purpose: >-
|
|
Läsa tillbaka en redan inlämnad momsdeklaration (inlamnat) och
|
|
Skatteverkets beslut (beslutat) för en period, på begäran av en
|
|
medlem i företaget via v1 REST-API:t eller MCP, så att bokföringen
|
|
kan stämmas av mot faktiskt inlämnade uppgifter. Läsning sker live
|
|
mot Skatteverket; deklarationsinnehållet lagras inte hos oss, endast
|
|
en audit-rad om att anropet gjordes.
|
|
lawful_basis: art_6_1_b # service provision: read-back requested by the customer
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na
|
|
data_subjects:
|
|
- business_owner
|
|
data_categories:
|
|
- user.government_id # orgnummer (juridiska personer) eller pnr
|
|
- user.financial.tax # rutor 05-62 i det inlämnade underlaget/beslutet
|
|
recipients:
|
|
- name: Skatteverket
|
|
country: SE
|
|
role: legal_recipient
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: Sweden-to-Sweden flow; no third-country transfer.
|
|
retention:
|
|
# Response bodies are returned to the caller and never persisted.
|
|
# Only the outbound call metadata lands in the audit log.
|
|
duration: 7y
|
|
basis: bfl_7_kap
|
|
stored_in:
|
|
- skatteverket_api_audit_log
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- rbac_company_member_role_check
|
|
- tls_1_3_to_skatteverket
|
|
- immutable_audit_log
|
|
- no_payload_body_persisted
|
|
- upstream_error_bodies_logged_server_side_only
|
|
|
|
- id: psd2.cash_account_mirror
|
|
name: PSD2-konton speglas till cash_accounts
|
|
purpose: >-
|
|
När en bank-anslutning via Enable Banking returnerar kontolista efter
|
|
lyckad PSD2-consent kopieras kontometadata (IBAN, valuta, kontonamn,
|
|
external_uid) till cash_accounts så att avstämning, motkontoresolver
|
|
och __PRIMARY_SEK__-sentinel kan rutta verifikat utan att läsa JSONB
|
|
från bank_connections.accounts_data vid varje fråga.
|
|
lawful_basis: art_6_1_b # contract (PSD2 consent + bookkeeping service)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na
|
|
data_subjects:
|
|
- business_owner
|
|
data_categories:
|
|
- user.financial.bank_account # IBAN
|
|
- user.contact # account name (kontotitel)
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor; no third-country transfer.
|
|
retention:
|
|
duration: consent_lifetime
|
|
basis: psd2_consent
|
|
stored_in:
|
|
- cash_accounts
|
|
- bank_connections.accounts_data
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- data_minimization_no_balance_on_callback
|
|
- failure_emitted_to_event_log
|
|
|
|
- id: transactions.counterparty_iban
|
|
name: Motpartens IBAN på transaktioner
|
|
purpose: >-
|
|
Spara motpartens IBAN på transaktionsraden så att own-account-detector
|
|
kan identifiera överföringar mellan företagets egna konton (1930 → 1932
|
|
etc.) och bokföra båda benen automatiskt istället för att felbokföra
|
|
utflödet som extern kostnad. Krävs även för payment-matchning mot
|
|
leverantörsfakturor.
|
|
lawful_basis: art_6_1_b # contract (bookkeeping service)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na
|
|
data_subjects:
|
|
- business_owner
|
|
- counterparty
|
|
data_categories:
|
|
- user.financial.bank_account # counterparty IBAN
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor.
|
|
retention:
|
|
duration: 7y
|
|
basis: bfl_7_kap
|
|
stored_in:
|
|
- transactions.counterparty_iban
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- immutable_after_post
|
|
|
|
- id: ai.inference
|
|
name: AI-inferens (kategorisering + dokumenttolkning) via Amazon Bedrock
|
|
purpose: >-
|
|
Föreslå bokföringskategori för banktransaktioner och tolka/extrahera
|
|
uppladdade underlag (kvitton, leverantörsfakturor) med Anthropic
|
|
Claude-modeller körda i Amazon Bedrock. Endast aktiv när tenanten
|
|
uttryckligen aktiverat AI-funktioner: kärntjänsten (bokföring, fakturor,
|
|
moms, rapporter) fungerar fullt ut utan AI.
|
|
lawful_basis: art_6_1_a # consent: opt-in, AI features off by default
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: aws-bedrock
|
|
data_subjects:
|
|
- business_owner
|
|
- counterparty # namn/belopp på uppladdade underlag
|
|
data_categories:
|
|
- user.financial # transaktionsdata skickad för kategorisering
|
|
- user.document # uppladdade kvitton/fakturor för OCR/extraktion
|
|
recipients:
|
|
- name: Amazon Web Services (Amazon Bedrock)
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: >-
|
|
Inferens körs i AWS-regionen eu-north-1 (Stockholm): ingen överföring
|
|
till tredje land. AWS DPA + SCC + DPF-certifiering finns som
|
|
skyddsmekanism. Prompter lagras ej hos Bedrock efter anropet och används
|
|
ej till modellträning.
|
|
retention:
|
|
duration: none_at_processor
|
|
basis: no_retention_bedrock # prompt not persisted by Bedrock post-inference
|
|
stored_in:
|
|
- ai_usage_tracking # usage metadata only (tokens/cost), no payload body
|
|
security_measures:
|
|
- opt_in_consent_required
|
|
- eu_region_inference_eu_north_1
|
|
- prompts_not_retained_after_inference
|
|
- not_used_for_model_training
|
|
- rls_company_scoped
|
|
- tls_to_bedrock
|
|
|
|
- id: mcp.telemetry
|
|
name: MCP/agent-telemetri i event_log
|
|
purpose: >-
|
|
Varje MCP-verktygsanrop loggar metadata (verktygsnamn, felkod,
|
|
felmeddelande max 500 tecken, latens, aktör, session, frivillig
|
|
distributionskanal-markör, t.ex. 'openclaw' via X-Gnubok-Client-header
|
|
eller ?client=-parameter, sanerad mot allow-list och aldrig använd för
|
|
auktorisation) och varje skill-laddning loggar slug/tier till event_log.
|
|
Syftet är tillförlitlighetsanalys av agentgränssnittet: felfrekvens per
|
|
verktyg, adoption per distributionskanal, korrelation mellan laddade
|
|
skills och efterföljande fel, samt agenters egenrapporterade feedback
|
|
(agent.feedback). Inga verktygsargument eller verktygsresultat
|
|
persisteras.
|
|
lawful_basis: art_6_1_f # legitimate interest (service reliability/improvement)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- business_owner
|
|
data_categories:
|
|
- user.unique_id # userId, actorId (API-nyckel-id), sessionId
|
|
- user.contact # actorLabel (användarvald API-nyckeletikett)
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor; no third-country transfer.
|
|
retention:
|
|
# Differentiated TTL via /api/events/cleanup/cron: mcp.*/agent.*-rader
|
|
# behålls 180 dagar (felfrekvens-trender kräver mer än leveransfönstret);
|
|
# övriga event_log-rader (leveranshändelser) 30 dagar.
|
|
duration: 180d
|
|
basis: legitimate_interest_reliability
|
|
stored_in:
|
|
- event_log
|
|
security_measures:
|
|
- rls_user_scoped_select # event_log SELECT: auth.uid() = user_id
|
|
- service_role_only_writes
|
|
- error_message_truncated_500_chars
|
|
- no_tool_args_or_results_persisted
|
|
|
|
- id: mcp.company_customer_tools
|
|
name: MCP-verktyg för företagsinställningar och kunder
|
|
purpose: >-
|
|
Låta en uttryckligen behörig användare läsa nödvändiga företags- och
|
|
kunduppgifter samt förbereda ändringar för användarens godkännande.
|
|
Verktygssökningen gnubok_search_tools returnerar endast verktygsmetadata
|
|
och JSON-schema, aldrig företags- eller kundposter. Läsresultat begränsas
|
|
till det aktiva företaget och till verktygets dokumenterade fält.
|
|
lawful_basis: art_6_1_b
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- business_owner
|
|
- company_member
|
|
- customer
|
|
data_categories:
|
|
- user.name
|
|
- user.contact
|
|
- user.financial.bank_account
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor.
|
|
retention:
|
|
duration: pending_operation_lifecycle
|
|
basis: contract_and_user_approval
|
|
stored_in:
|
|
- pending_operations
|
|
security_measures:
|
|
- api_key_scope_companies_read_or_write
|
|
- api_key_scope_customers_read_or_write
|
|
- active_company_filter_on_every_query
|
|
- rls_company_scoped
|
|
- search_returns_tool_metadata_only
|
|
- writes_staged_for_explicit_user_approval
|
|
- plaintext_personnummer_blocked_from_staging
|
|
|
|
- id: mcp.async_task_handles
|
|
name: MCP-uppgiftshandtag för långkörande verktygsanrop
|
|
purpose: >-
|
|
Låta en behörig MCP-klient hämta resultatet av ett långkörande
|
|
verktygsanrop (t.ex. generering av arkivpaket) via ett hållbart
|
|
uppgifts-id i stället för att hålla anslutningen öppen. Raden lagrar
|
|
verktygets svar tills klienten hämtat det; endast den skapande
|
|
användaren kan läsa, och utgångna rader rensas löpande vid nya anrop.
|
|
lawful_basis: art_6_1_b
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- business_owner
|
|
- company_member
|
|
data_categories:
|
|
- user.financial
|
|
- user.activity_timestamp
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor.
|
|
retention:
|
|
duration: 1h
|
|
basis: transient_tool_call_state_storage_limitation
|
|
stored_in:
|
|
- mcp_tasks
|
|
security_measures:
|
|
- rls_user_scoped_select # mcp_tasks SELECT: auth.uid() = user_id
|
|
- service_role_only_writes
|
|
- creator_scoped_task_polling # tasks/get requires the creating user
|
|
- expired_rows_purged_on_creation
|
|
|
|
- id: arsredovisning.signature_evidence
|
|
name: Underskriftsbevis för årsredovisning
|
|
purpose: >-
|
|
Bevara vem som undertecknade en låst årsredovisningsversion, i vilken
|
|
roll, med vilken metod och på vilket datum. Bevisreferensen är endast en
|
|
ogenomskinlig identifierare med prefix archive:, document: eller receipt:.
|
|
Den får inte innehålla namn, e-post, personnummer eller fritext.
|
|
lawful_basis: art_6_1_c
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- company_signatory
|
|
data_categories:
|
|
- user.name
|
|
- user.professional_role
|
|
- user.activity_timestamp
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: EU-only processor.
|
|
retention:
|
|
duration: 7y
|
|
basis: bfl_7_kap_and_arl_signing_evidence
|
|
stored_in:
|
|
- arsredovisning_signature_requests
|
|
- annual_report_versions
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- service_role_only_signature_transitions
|
|
- version_company_period_and_state_validation
|
|
- signed_rows_immutable
|
|
- structured_opaque_evidence_reference
|
|
- future_and_pre_finalization_dates_rejected
|
|
|
|
- id: arsredovisning.bolagsverket.submit
|
|
name: Digital inlämning av årsredovisning till Bolagsverket
|
|
purpose: >-
|
|
Lämna in årsredovisningen (iXBRL) digitalt till Bolagsverkets eget
|
|
utrymme (ÅRL 8 kap.). Avsändarens och undertecknarens personnummer
|
|
krävs av Bolagsverkets API (skapa-inlamningtoken, lamna-in) och
|
|
används endast transient i anropen. Varken personnummer eller hash av
|
|
personnummer sparas i inlämningsposten. Fastställelseintyget signeras
|
|
med e-legitimation hos Bolagsverket, aldrig i appen.
|
|
lawful_basis: art_6_1_c # legal obligation (ÅRL filing duty)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: anthropic-na # software supplier; Bolagsverket is recipient, not processor
|
|
data_subjects:
|
|
- business_owner # avsändare/undertecknare (styrelseledamot, VD)
|
|
data_categories:
|
|
- user.government_id # personnummer, endast transient till Bolagsverket
|
|
- user.name # undertecknarens namn
|
|
- user.contact # undertecknarens/kvittens e-post
|
|
- user.financial # årsredovisningens finansiella innehåll
|
|
recipients:
|
|
- name: Bolagsverket
|
|
country: SE
|
|
role: legal_recipient
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: Sweden-to-Sweden flow to a public authority; no third-country transfer.
|
|
retention:
|
|
duration: 7y
|
|
basis: bfl_7_kap # filed .xhtml archived as räkenskapsinformation
|
|
stored_in:
|
|
- arsredovisning_submissions # status, idnummer and checksums, no PNR values or hashes
|
|
- document_attachments # the exact filed iXBRL bytes (WORM)
|
|
security_measures:
|
|
- mtls_client_certificate_to_bolagsverket
|
|
- mtls_key_env_only_injected_from_secret_manager # never in settings/DB; see .env.example custody note
|
|
- pinned_endpoint_allowlist_per_environment # HOSTS map in client.ts; not env-configurable
|
|
- personnummer_and_hash_never_persisted
|
|
- personnummer_never_logged
|
|
- webhook_secret_constant_time_validation
|
|
- rls_company_scoped
|
|
- immutable_status_machine_trigger
|
|
|
|
- id: product.analytics
|
|
name: Produktanalys, sessionsinspelning, felrapportering, enkäter och supportärenden (PostHog)
|
|
purpose: >-
|
|
Förstå hur tjänsten faktiskt används (var användare fastnar i
|
|
onboarding, vilka flöden som avbryts), fånga klientfel och serverfel så
|
|
de kan åtgärdas, och ställa riktade produktfrågor. Endast hostad drift:
|
|
self-hosted-installationer laddar aldrig PostHog (isAnalyticsEnabled()
|
|
kortsluter på NEXT_PUBLIC_SELF_HOSTED, och ingen token bakas in i
|
|
Docker-imagen). Sandbox-/demoföretag identifieras aldrig.
|
|
lawful_basis: art_6_1_f # legitimate interest (produktförbättring och felsökning)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: posthog-eu
|
|
data_subjects:
|
|
- business_owner
|
|
- company_member
|
|
data_categories:
|
|
- user.contact.email # person property via identify(), aldrig i event-properties
|
|
- user.name # profiles.full_name
|
|
# Sessionsinspelningen är maskerad som standard: all tangentbords-
|
|
# inmatning och all text maskeras, utom appens eget statiska gränssnitt
|
|
# (rubriker, navigering, knappar, etiketter), se security_measures.
|
|
- user.behavior # sidvisningar, klick, händelser, sessionsinspelning
|
|
- user.device # user agent, skärmstorlek, IP (trunkeras av PostHog)
|
|
# Supportärenden: fritext som användaren SJÄLV skriver till supporten via
|
|
# in-app-dialogen (components/ui/support-link.tsx). Till skillnad från
|
|
# analysdata bär ärendet meddelandetexten, eftersom det är hela poängen
|
|
# med att skriva till supporten. Kan i praktiken innehålla bokförings-
|
|
# detaljer som användaren väljer att beskriva. Widgeten är avstängd:
|
|
# endast vår egen dialog kan öppna ärenden.
|
|
- user.content.support # fritext i supportmeddelanden, användarinitierat
|
|
# EJ organisationsnummer: för enskild firma ÄR orgnr innehavarens
|
|
# personnummer. buildGroupProperties() vägrar skicka det och ett
|
|
# enhetstest låser fast beteendet.
|
|
recipients:
|
|
- name: PostHog
|
|
country: DE
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: >-
|
|
PostHog Cloud EU (Frankfurt). EU-baserat biträde; ingen
|
|
tredjelandsöverföring. DPA tecknat; SCCs gäller för eventuella
|
|
underbiträden utanför EES.
|
|
retention:
|
|
duration: posthog_project_retention
|
|
basis: processor_configured # sätts i PostHog-projektet, ej i denna kodbas
|
|
stored_in:
|
|
- posthog_eu # externt hos biträdet; inget lagras i vår databas
|
|
security_measures:
|
|
# Maskerat-som-standard (2026-08-17, ersätter det mönsterbaserade läget
|
|
# från 2026-08-06): ALL tangentbordsinmatning maskeras (rrweb
|
|
# maskAllInputs utan undantagsfunktion) och ALL text maskeras utom
|
|
# element taggade data-ph-unmask (statiskt i18n-gränssnitt: rubriker,
|
|
# navigering, etiketter, knappar) samt tabellkolumnrubriker. Även
|
|
# otaggat gränssnitt maskeras, så felmoden för ny UI är övermaskering,
|
|
# aldrig läckage. Taggat gränssnitt mönsterskrubbas dessutom på belopp
|
|
# och person-/organisationsnummer. data-ph-mask tvingar maskering och
|
|
# vinner över data-ph-unmask; låst av test i lib/analytics/__tests__.
|
|
- session_replay_masked_by_default # lib/analytics/replay-masking.ts
|
|
- session_replay_masks_all_input_values
|
|
- session_replay_masks_amounts_and_identity_numbers_in_chrome
|
|
- session_replay_masks_password_inputs
|
|
- org_number_never_transmitted_as_group_property # låst av test i lib/analytics/__tests__
|
|
- no_pii_in_event_properties # PII endast som person properties via identify()
|
|
# persistence: 'memory' -> ingen analysdata och inga kakor på enheten.
|
|
# TVÅ undantag, båda funktionell UI-state utan personuppgifter, båda
|
|
# skrivna direkt till localStorage av PostHogs egna moduler förbi
|
|
# persistence-inställningen: seenSurvey_<id> ("visa inte igen") och
|
|
# ph_conv_<token> = {"widgetSessionId": uuid} (hitta tillbaka till sitt
|
|
# eget ärende i samma webbläsare). ph_conv skapas vid sidladdning, alltså
|
|
# innan användaren faktiskt kontaktar supporten. Verifierat i prod.
|
|
- no_analytics_data_or_cookies_on_device
|
|
- only_functional_ui_state_persisted # seenSurvey_*, ph_conv_* (uuid, ingen PII)
|
|
# Radering (A.8.10), se .compliance/Data_Classification_Handling.md:
|
|
# ph_conv_* raderas vid utloggning (resetAnalyticsIdentity ->
|
|
# posthog.reset() -> conversations-managerns egen reset). seenSurvey_*
|
|
# har ingen raderingstrigger: ingen PostHog-bundle itererar över
|
|
# localStorage, så SDK:n kan inte hitta nycklarna. Accepterat: värdet är
|
|
# "true" under ett ogenomskinligt survey-id, utan personuppgifter.
|
|
- ph_conv_cleared_on_logout # hindrar delad enhet från att ärva ärendesession
|
|
- same_origin_reverse_proxy # /rl-rewrite; ingen tredjepartsvärd i CSP
|
|
- sandbox_companies_never_identified
|
|
- disabled_entirely_when_self_hosted
|
|
- server_side_payloads_redacted_before_send # lib/observability/redact.ts
|
|
- support_widget_disabled # endast egen UI via conversations-API
|
|
- support_ticket_identity_hmac_signed # POSTHOG_SECRET_API_KEY, serverside
|
|
- support_message_body_never_in_event_properties # breadcrumb bär endast subject
|
|
- email_remains_the_delivery_guarantee # ärendet är ett komplement, ej leveransväg
|
|
|
|
- id: whatsapp.receipt_intake
|
|
name: Kvittoinlämning och klargörande dialog via WhatsApp
|
|
purpose: >-
|
|
Ta emot kvitton som foto eller PDF skickade till Accounteds delade
|
|
WhatsApp-nummer och landa dem i dokumentinkorgen (Underlag) som
|
|
räkenskapsinformation, samt föra en kort klargörande dialog i samma
|
|
chatt: bekräfta mottagning, fråga vilket företag underlaget tillhör och
|
|
samla in de representationsuppgifter Skatteverket kräver (deltagarnas
|
|
namn och företag samt syfte). Avsändare är enbart befintliga
|
|
Accounted-användare som själva knutit sitt nummer med en engångskod;
|
|
okända nummer får ett enda begränsat standardsvar och behandlas aldrig
|
|
vidare (ingen AI, ingen mediahämtning, inget innehåll sparas).
|
|
Deltagarnamn i representationssvar är tredjepartsuppgifter som samlas
|
|
in från användaren, inte från de registrerade själva; individuell
|
|
information till varje deltagare vore oproportionerlig i förhållande
|
|
till den lagstadgade dokumentationsplikten och undantaget i art.
|
|
14.5 b tillämpas (uppgifterna framgår av verifikationen, som bevaras
|
|
enligt BFL).
|
|
# art 6.1 b: tjänsten (mottagning, dialog, inkorgsleverans).
|
|
# art 6.1 c: BFL 7 kap: kvittot bevaras 7 år som räkenskapsinformation.
|
|
# art 6.1 f: tredjeparts deltagarnamn i representationssvar (med
|
|
# art 14.5 b-undantaget dokumenterat i purpose ovan).
|
|
lawful_basis: art_6_1_b_c_and_f
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase_and_meta
|
|
data_subjects:
|
|
- business_owner
|
|
- company_member
|
|
- representation_attendee # tredjepart: namn + företag i representationssvar
|
|
data_categories:
|
|
- user.contact.phone_number # HMAC-hash med peppar + AES-256-GCM + mask; aldrig i klartext i DB
|
|
- user.communication_content # chattext och webhook-payload utan avsändarnummer, 90 dagars retention
|
|
- user.document # kvittofiler, 7 år WORM enligt BFL 7 kap
|
|
- user.name # deltagarnamn i representationssvar (tredjepart)
|
|
- user.activity_timestamp
|
|
recipients:
|
|
- name: Meta Platforms Ireland Ltd (WhatsApp Business Platform, Cloud API)
|
|
country: IE
|
|
role: processor
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
# AI-tolkningen av kvittobilder och fritextsvar sker via Amazon Bedrock
|
|
# och täcks av den befintliga aktiviteten ai.inference ovan.
|
|
international_transfers:
|
|
applicable: true
|
|
mechanism: scc_2021_c2p
|
|
note: >-
|
|
Meta Platforms Ireland behandlar meddelandena under WhatsApp Business
|
|
dataskyddsvillkor med EU SCC-addendum (Module 2) för vidareöverföring
|
|
till Meta Platforms Inc. (US). Cloud API Local Storage är aktiverat
|
|
med region DE, så meddelandeinnehåll lagras i vila inom EU hos Meta.
|
|
retention:
|
|
# Differentierad per datakategori; verkställs dagligen av
|
|
# /api/extensions/whatsapp-inbox/retention/cron:
|
|
# chattranskript (body_text, raw_payload) 90 dagar, sedan rensas
|
|
# innehållet men radskelettet (wamid, riktning, tidsstämplar,
|
|
# status, inbox-koppling) bevaras som behandlingsspår
|
|
# okända avsändares rader 30 dagar, sedan radering
|
|
# engångskoder 24 h efter utgång, radering
|
|
# telefonkoppling (phone_enc) tills användaren återkallar,
|
|
# därefter kryptoshredning 90 dagar efter revokering (phone_hash och
|
|
# phone_masked bevaras för unikhetshistorik respektive visning)
|
|
# kvittofilen 7 år, WORM (BFL 7 kap),
|
|
# hanteras av dokumentarkivet, ej av denna cron
|
|
# kontoradering (art 17) omedelbart, inte via cron:
|
|
# anonymize_user_account revokerar och kryptoshreddar kopplingen,
|
|
# nollställer body_text/raw_payload på kopplingens meddelanden och
|
|
# raderar engångskoder (migration 20260803090000). auth.users
|
|
# gravsätts i stället för att raderas, så tabellens ON DELETE
|
|
# CASCADE utlöses aldrig av sig själv
|
|
duration: differentiated_per_data_category
|
|
basis: gdpr_storage_limitation_and_bfl_7_kap
|
|
stored_in:
|
|
- whatsapp_messages
|
|
- whatsapp_phone_links
|
|
- whatsapp_link_codes
|
|
- whatsapp_sender_rate_counters
|
|
- whatsapp_conversations
|
|
- invoice_inbox_items.channel_context
|
|
- document_attachments # kvittofilen, WORM
|
|
security_measures:
|
|
- phone_number_hmac_peppered_hash_lookup_only
|
|
- phone_number_aes_256_gcm_at_rest
|
|
- phone_number_masked_in_ui
|
|
- dedicated_whatsapp_key_material_not_bankid_keys
|
|
- webhook_signature_verified_over_raw_body
|
|
- service_role_only_tables_rls_no_policies
|
|
- unknown_senders_no_content_persistence_no_llm
|
|
- pre_binding_sender_rate_limits
|
|
- daily_retention_cron_with_crypto_shred
|
|
- llm_reads_answers_as_untrusted_data_no_tools
|
|
- receipt_files_worm_protected_bfl
|
|
- opt_out_stop_keyword_honored
|
|
- muted_senders_no_content_persistence
|
|
- account_deletion_revokes_and_shreds_whatsapp_link
|
|
|
|
- id: notifications.bookkeeping_digest
|
|
name: Daglig e-postsammanfattning "nytt att bokföra"
|
|
purpose: >-
|
|
Skicka ett dagligt opt-in-mejl till företagsmedlemmar när nya
|
|
banktransaktioner eller inkorgsunderlag har kommit in och väntar på
|
|
bokföring. Mejlet innehåller endast antal per kategori och en länk till
|
|
appen: aldrig belopp, motparter eller transaktionsinnehåll.
|
|
lawful_basis: art_6_1_a
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase_and_resend
|
|
data_subjects:
|
|
- company_member
|
|
data_categories:
|
|
- user.contact.email
|
|
- user.financial_activity_counts
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
- name: Resend
|
|
country: US
|
|
role: processor
|
|
international_transfers:
|
|
applicable: true
|
|
mechanism: scc_2021_c2p
|
|
note: Resend processes the outbound delivery under SCC Module 2.
|
|
retention:
|
|
duration: notification_log_row_retained_with_table
|
|
basis: gdpr_storage_limitation
|
|
stored_in:
|
|
- notification_log
|
|
- notification_settings
|
|
security_measures:
|
|
- opt_in_default_false_user_toggle_only
|
|
- counts_only_no_amounts_or_counterparties_in_body
|
|
- recipient_restricted_to_active_company_members
|
|
- claim_based_once_per_day_dedup
|
|
- company_name_header_injection_sanitized
|
|
- cron_secret_authenticated_trigger
|
|
|
|
- id: bookkeeping.duplicate_dismissal_history
|
|
name: Behandlingshistorik för åsidosatt dubblettkontroll
|
|
purpose: >-
|
|
Bevara en oföränderlig post i behandlingshistoriken (processing_history,
|
|
händelsetyp BankTransactionDuplicateDismissed) när en användare eller en
|
|
API-nyckel bokför en banktransaktion trots att dubblettkontrollen visade
|
|
att bokförda verifikat redan förklarar den (force=true i dashboarden,
|
|
i MCP-verktyget eller vid godkännande av en väntande operation). Posten
|
|
gör beslutet rekonstruerbart för revisor och Skatteverket: vilka
|
|
verifikat som åsidosattes, belopp, konto, datum och vem som fattade
|
|
beslutet. Innehåller endast interna id:n, verifikationsnummer, belopp
|
|
och datum; aldrig motpartsnamn, transaktionsbeskrivningar eller
|
|
personnummer.
|
|
lawful_basis: art_6_1_c # BFL 5 kap. 11 § + BFNAR 2013:2 p. 9.16 (behandlingshistorik)
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- company_member # the user (or API-key owner) who honoured the override
|
|
data_categories:
|
|
- user.activity_timestamp
|
|
- user.financial.transaction # amount + settlement account of the bank row, no counterparty
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
retention:
|
|
duration: through_seventh_calendar_year_after_fiscal_year_end
|
|
basis: bfl_7_kap_and_gdpr_storage_limitation
|
|
stored_in:
|
|
- processing_history
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- service_role_insert_only_no_update_or_delete_triggers
|
|
- personnummer_pattern_rejected_at_append
|
|
- ids_labels_amounts_dates_only_never_descriptions_or_counterparties
|
|
- actor_recorded_as_pseudonymous_user_or_api_key_id
|
|
- written_only_after_the_booking_succeeded
|
|
|
|
- id: documents.own_company_supplier_guard
|
|
name: Egenbolagskontroll vid dokumentextraktion
|
|
purpose: >-
|
|
Jämföra AI-extraherade leverantörsfält mot det egna bolagets namn och
|
|
organisationsnummer (för enskild firma ett personnummer) så att
|
|
användarens eget bolag aldrig föreslås som leverantör när modellen läst
|
|
dokumentets kundblock. Jämförelsen sker i applikationskoden efter
|
|
extraktionen; identiteten skickas aldrig till AI-modellen och skrivs
|
|
inte till någon ny lagringsyta.
|
|
lawful_basis: art_6_1_f
|
|
special_category_basis: null
|
|
controller: gnubok-tenant
|
|
processor: supabase
|
|
data_subjects:
|
|
- business_owner
|
|
data_categories:
|
|
- user.name # companies.name
|
|
- user.government_id # companies.org_number, personnummer för enskild firma
|
|
recipients:
|
|
- name: Supabase
|
|
country: EU
|
|
role: processor
|
|
international_transfers:
|
|
applicable: false
|
|
mechanism: null
|
|
note: >-
|
|
Transient jämförelse i applikationsprocessen; datat lämnar aldrig
|
|
befintlig lagring (companies-tabellen) och skickas inte till Bedrock.
|
|
retention:
|
|
duration: none
|
|
basis: transient_in_memory_comparison
|
|
stored_in: []
|
|
security_measures:
|
|
- rls_company_scoped
|
|
- not_sent_to_model
|
|
- transient_comparison_no_new_storage
|
|
- fail_open_to_nulls_on_lookup_error
|