Files
boc/docs/arc/ARC-001-canonical-domain-model.md
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

430 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ARC-001 — Canonical Domain Model
**Status:** Accepted
**Datum:** 2026-06-01
**Beslutsfattare:** AAMOS Architecture Team
---
## Kontext
AAMOS är ett verksamhetsoperativsystem vars primära designkrav är att moduler (Ekonomi, CRM, HR, Projekt, Kvalitet, Inköp, Kontrakt) kan samexistera, utbyta data och bygga på varandras entiteter utan att skapa n×m-mappningsproblem.
Utan ett kanoniskt objektsystem uppstår ofrånkomligen:
- **Semantisk drift** — samma begrepp (t.ex. "Person") modelleras på 4 olika sätt i 4 moduler, vilket omöjliggör korsmodulärsökning och AI-kontext
- **Integrationsskuld** — varje ny modul måste skriva egna adaptrar mot alla befintliga moduler
- **Auditbrist** — när ett objekt existerar på flera ställen blir spårbarhet svårt att garantera
Ekonomimodulen är referensimplementationen. Alla val i detta ADR speglas redan (eller ska speglas) i Ekonomimodulens databasschema.
---
## Beslut
AAMOS definierar tolv universella kanoniska objekt. Varje modul **MÅSTE** använda dessa objekt som primärreferenser och får inte definiera alternativa entiteter för samma semantiska koncept.
### Fundamentala fält (alla objekt)
Varje kanoniskt objekt bär alltid dessa fält:
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `id` | `UUID v7` | Globalt unikt, tidsordnat |
| `tenant_id` | `UUID` | Isoleringsdomän (se ARC-003) |
| `created_at` | `timestamptz` | ISO 8601, UTC |
| `updated_at` | `timestamptz` | ISO 8601, UTC, auto-update |
| `created_by` | `UUID` | Referens till Person.id eller system-ID |
| `deleted_at` | `timestamptz\|null` | Soft-delete, null = aktiv |
| `metadata` | `jsonb` | Fri nyckel-värde-extension (modul-specifika fält) |
| `version` | `integer` | Optimistic locking, inkrementeras vid varje UPDATE |
> **UUID v7** används för att primary key-index förblir klusterordnade i PostgreSQL och förhindrar page splits vid hög insertfrekvens.
---
### Objekt 1 — Entity
> Abstrakt basobjekt. Alla övriga kanoniska objekt **ärver** Entity-kontraktet (de fundamentala fälten ovan). Entity instansieras aldrig direkt — det är ett kontrakt, inte en tabell.
**Syfte:** Definiera det minsta kontraktet som gör ett objekt spårbart, isolerat och versionerat.
**Relationer:**
- Varje konkret objekt implementerar Entity-kontraktet
- Entity-kontraktet gör det möjligt för generiska tjänster (audit, sök, event-routing) att hantera alla objekt uniformt
---
### Objekt 2 — Organization
> Bolag, juridisk person, koncern, avdelning, team, kostnadsställe.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `name` | `text` | Officiellt namn |
| `org_number` | `text\|null` | Organisationsnummer (landsspecifikt) |
| `org_type` | `enum` | `company`, `department`, `team`, `group`, `subsidiary` |
| `parent_id` | `UUID\|null` | Självrefererande hierarki |
| `country_code` | `char(2)` | ISO 3166-1 alpha-2 |
| `tax_id` | `text\|null` | Momsregistreringsnummer |
| `currency_code` | `char(3)` | ISO 4217 (primärvaluta) |
| `status` | `enum` | `active`, `inactive`, `merged`, `dissolved` |
| `external_refs` | `jsonb` | `{erp_id, crm_id, ...}` |
**Relationer:**
- Organization → Organization (parent_id, hierarkisk)
- Organization ↔ Person (many-to-many via `memberships`)
- Organization → Location (many)
- Organization → Contract (many)
**Ekonomimodulen-mappning:**
`accounts.owner_org_id → Organization.id`
`journal_entries.entity_id → Organization.id` (när motpart är bolag)
---
### Objekt 3 — Person
> Anställd, kontaktperson, ägare, kund, leverantörskontakt, systemanvändare.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `first_name` | `text` | |
| `last_name` | `text` | |
| `display_name` | `text` | Beräknat eller angivet |
| `email` | `text\|null` | Primär e-post |
| `phone` | `text\|null` | E.164-format |
| `national_id` | `encrypted_text\|null` | Personnummer — krypterat i vila |
| `person_type` | `enum` | `employee`, `contact`, `customer`, `owner`, `system` |
| `primary_org_id` | `UUID\|null` | → Organization.id |
| `user_account_id` | `UUID\|null` | → auth-systemets User.id |
| `gdpr_consent` | `jsonb` | Samtyckesregister per ändamål |
**Relationer:**
- Person → Organization (many-to-many via memberships)
- Person → Location (many, adress-koppling)
- Person → Task (assignee)
- Person → Contract (signatory)
**Ekonomimodulen-mappning:**
`suppliers.contact_person_id → Person.id`
`expense_reports.submitted_by → Person.id`
---
### Objekt 4 — Asset
> Anläggningstillgång, resursobjekt, produkt, inventariepost, licenspost.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `name` | `text` | |
| `asset_type` | `enum` | `fixed_asset`, `inventory`, `product`, `license`, `resource`, `intangible` |
| `serial_number` | `text\|null` | |
| `acquisition_date` | `date\|null` | |
| `acquisition_cost` | `numeric(18,4)\|null` | |
| `currency_code` | `char(3)\|null` | |
| `owner_org_id` | `UUID\|null` | → Organization.id |
| `location_id` | `UUID\|null` | → Location.id |
| `status` | `enum` | `active`, `disposed`, `under_maintenance`, `transferred` |
| `depreciation_policy` | `jsonb\|null` | Avskrivningsregler (Ekonomi) |
**Relationer:**
- Asset → Organization (owner)
- Asset → Location (plats)
- Asset → Transaction (inköp, avskrivning)
- Asset → Contract (leasing, garanti)
**Ekonomimodulen-mappning:**
`fixed_assets` tabell är en direkt implementation av Asset med utökad `depreciation_policy`.
---
### Objekt 5 — Location
> Adress, arbetsplats, lagerplats, land, leveransadress.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `name` | `text\|null` | T.ex. "Huvudkontor Stockholm" |
| `location_type` | `enum` | `address`, `workplace`, `warehouse`, `country`, `virtual` |
| `street` | `text\|null` | |
| `city` | `text\|null` | |
| `postal_code` | `text\|null` | |
| `country_code` | `char(2)` | ISO 3166-1 |
| `region` | `text\|null` | Stat/landsdel |
| `coordinates` | `point\|null` | PostGIS `POINT(lon lat)` |
| `parent_id` | `UUID\|null` | Hierarki (land → stad → adress) |
**Relationer:**
- Location → Organization (many)
- Location → Person (hemadress, arbetsplats)
- Location → Asset (plats)
---
### Objekt 6 — Project
> Projekt, uppdrag, kostnadsbärare, kampanj, budgetpost.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `name` | `text` | |
| `code` | `text\|null` | Projektnamn/nummer för referens |
| `project_type` | `enum` | `internal`, `client`, `rnd`, `infrastructure`, `overhead` |
| `status` | `enum` | `planning`, `active`, `on_hold`, `completed`, `cancelled` |
| `owner_id` | `UUID` | → Person.id |
| `org_id` | `UUID` | → Organization.id |
| `budget` | `numeric(18,4)\|null` | |
| `currency_code` | `char(3)\|null` | |
| `start_date` | `date\|null` | |
| `end_date` | `date\|null` | |
| `parent_project_id` | `UUID\|null` | Delprojekt-hierarki |
**Relationer:**
- Project → Organization (ägande)
- Project → Person (projektledare, team)
- Project → Task (nedbrytning)
- Project → Transaction (kostnader, intäkter)
- Project → Contract (kontrakt kopplade till projekt)
**Ekonomimodulen-mappning:**
`cost_centers.project_id → Project.id`
`budget_lines.project_id → Project.id`
---
### Objekt 7 — Task
> Arbetsuppgift, aktivitet, ärende, batchjobb, processuppgift.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `title` | `text` | |
| `description` | `text\|null` | |
| `task_type` | `enum` | `manual`, `automated`, `approval`, `review`, `notification` |
| `status` | `enum` | `pending`, `in_progress`, `blocked`, `completed`, `cancelled` |
| `priority` | `enum` | `low`, `medium`, `high`, `critical` |
| `assignee_id` | `UUID\|null` | → Person.id |
| `project_id` | `UUID\|null` | → Project.id |
| `parent_task_id` | `UUID\|null` | Subtask-hierarki |
| `due_date` | `timestamptz\|null` | |
| `completed_at` | `timestamptz\|null` | |
| `workflow_instance_id` | `UUID\|null` | → Workflow.id |
**Relationer:**
- Task → Person (assignee, creator)
- Task → Project (tillhörighet)
- Task → Workflow (processdrivet)
- Task → Document (bilagor)
---
### Objekt 8 — Workflow
> Processinstans. Kopplas till Workflow Engine (framtida).
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `workflow_definition_id` | `text` | Referens till process-definition |
| `workflow_version` | `text` | Versionerad processdefinition |
| `status` | `enum` | `pending`, `running`, `waiting`, `completed`, `failed`, `cancelled` |
| `context` | `jsonb` | Runtime-kontext för processinstansen |
| `started_at` | `timestamptz\|null` | |
| `completed_at` | `timestamptz\|null` | |
| `initiator_id` | `UUID` | → Person.id eller system |
| `current_step` | `text\|null` | Stegidentifierare |
**Relationer:**
- Workflow → Task (genererade uppgifter)
- Workflow → Document (processade dokument)
- Workflow → Transaction (utlösta transaktioner)
**Ekonomimodulen-mappning:**
Fakturaflöde (inkommen faktura → godkännande → betalning) är en Workflow-instans.
---
### Objekt 9 — Document
> Faktura, kvitto, kontrakt, rapport, fil, attest.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `title` | `text` | |
| `document_type` | `enum` | `invoice`, `receipt`, `contract`, `report`, `attachment`, `specification`, `certificate` |
| `mime_type` | `text\|null` | `application/pdf`, `image/jpeg`, etc. |
| `storage_key` | `text\|null` | S3/blob-sökväg |
| `checksum` | `text\|null` | SHA-256 av filinnehållet |
| `size_bytes` | `bigint\|null` | |
| `source_system` | `text\|null` | Ursprungssystem |
| `related_entity_id` | `UUID\|null` | Polymorf referens |
| `related_entity_type` | `text\|null` | T.ex. `Transaction`, `Contract` |
| `classification` | `enum` | `public`, `internal`, `confidential`, `restricted` (se ARC-003) |
| `extracted_data` | `jsonb\|null` | AI-extraherade fält (se ARC-002) |
**Relationer:**
- Document → Transaction (underlag)
- Document → Contract (kontraktsdokument)
- Document → Workflow (processunderlag)
**Ekonomimodulen-mappning:**
`supplier_invoices.document_id → Document.id`
`expense_reports.receipt_document_ids → Document.id[]`
---
### Objekt 10 — Transaction
> Ekonomisk händelse. `JournalEntry` är en Transaction med `transaction_type = journal_entry`.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `transaction_type` | `enum` | `journal_entry`, `payment`, `invoice`, `credit_note`, `accrual`, `depreciation`, `transfer` |
| `amount` | `numeric(18,4)` | Absolutbelopp |
| `currency_code` | `char(3)` | ISO 4217 |
| `base_currency_amount` | `numeric(18,4)\|null` | Omräknat till baskvaluta |
| `exchange_rate` | `numeric(12,8)\|null` | |
| `status` | `enum` | `draft`, `posted`, `voided`, `pending_approval` |
| `transaction_date` | `date` | Transaktionsdatum (affärsdatum) |
| `value_date` | `date\|null` | Valuteringsdatum |
| `reference` | `text\|null` | Externt referensnummer |
| `counterparty_id` | `UUID\|null` | → Organization.id eller Person.id |
| `project_id` | `UUID\|null` | → Project.id |
| `document_id` | `UUID\|null` | → Document.id (underlag) |
| `lines` | `jsonb` | Transaktionsrader med konton och belopp |
**Relationer:**
- Transaction → Organization (motpart)
- Transaction → Person (utförare)
- Transaction → Document (verifikat)
- Transaction → Project (kostnadsbärare)
- Transaction → Asset (anläggningstillgångsrörelser)
**Ekonomimodulen-mappning:**
`journal_entries` är Transactions med `transaction_type = journal_entry`.
Ledger Engine håller transaktionsrader i `lines`-jsonb eller separata rader beroende på volym.
---
### Objekt 11 — Event
> Systemevent, domänevent. Detta är Hermes Event Fabric-payloaden.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `event_type` | `text` | Namnrymd: `aamos.ekonomi.transaction.posted` |
| `event_version` | `text` | Semver av event-schemat |
| `source_service` | `text` | Originerande tjänst |
| `correlation_id` | `UUID` | Spårnings-ID genom systemet |
| `causation_id` | `UUID\|null` | ID på det event som orsakade detta |
| `subject_type` | `text` | Typ av ämnesobjekt |
| `subject_id` | `UUID` | ID på ämnesobjektet |
| `payload` | `jsonb` | Eventnyttolast |
| `published_at` | `timestamptz` | Publiceringstidpunkt |
**Namnrymdskonvention:**
`aamos.<modul>.<objekt>.<verb>` — t.ex. `aamos.ekonomi.transaction.posted`, `aamos.crm.contact.created`
**Relationer:**
- Event refererar till vilket som helst kanoniskt objekt via `subject_id`
- Events persisteras i JSONL-fallback för replay
---
### Objekt 12 — Contract
> Avtal, SLA, leverantörsavtal, anställningsavtal, ramavtal, licens.
| Fält | Typ | Beskrivning |
|------|-----|-------------|
| `title` | `text` | |
| `contract_type` | `enum` | `supplier`, `customer`, `employment`, `sla`, `license`, `framework`, `nda` |
| `status` | `enum` | `draft`, `pending_signature`, `active`, `expired`, `terminated`, `renewed` |
| `counterparty_org_id` | `UUID\|null` | → Organization.id |
| `counterparty_person_id` | `UUID\|null` | → Person.id |
| `owner_org_id` | `UUID` | → Organization.id (intern part) |
| `start_date` | `date\|null` | |
| `end_date` | `date\|null` | |
| `auto_renewal` | `boolean` | |
| `value` | `numeric(18,4)\|null` | Kontraktsvärde |
| `currency_code` | `char(3)\|null` | |
| `document_id` | `UUID\|null` | → Document.id (signerat PDF) |
| `terms` | `jsonb\|null` | Strukturerade avtalsvillkor |
**Relationer:**
- Contract → Organization (parter)
- Contract → Person (signatärer)
- Contract → Document (kontraktsdokument)
- Contract → Transaction (betalningsplan, fakturor)
- Contract → Asset (leasade tillgångar)
---
## Ekonomimodulens fullständiga mappning
```
Ekonomimodul → Kanonisk typ
──────────────────────────────────────────
JournalEntry → Transaction (type=journal_entry)
Supplier → Organization (org_type=company)
Customer → Organization (org_type=company)
SupplierInvoice → Document (type=invoice) + Transaction
ExpenseReport → Document (type=report) + Transaction[]
FixedAsset → Asset (type=fixed_asset)
CostCenter → Project (type=overhead/internal)
Employee → Person (type=employee)
ApprovalFlow → Workflow
Receipt → Document (type=receipt)
SupplierContract → Contract (type=supplier)
```
---
## Konsekvenser
### Positiva
- Ny modul definierar bara sina egna **extensions** i `metadata`, inte nya bastyper
- AI-kontextfönster kan laddas med enhetliga objektrepresentationer
- Korsmodulär sökning och audit fungerar utan adaptrar
- Event-routing är självbeskrivande via `subject_type`
### Negativa / risker
- **Migrationsarbete:** Ekonomimodulen måste verifiera att befintliga tabeller uppfyller kontraktet
- **UUID v7 kräver** att databasen kan generera dem (`pgcrypto` eller app-sida)
- `metadata jsonb` kan bli en dumpingsplats — kräver disciplin och review
---
## Implementation
### Fas 1 (Ekonomimodulen, nu)
1. Verifiera att alla tabeller har `tenant_id`, `version`, `deleted_at`
2. Lägg till `metadata jsonb DEFAULT '{}'` på tabeller som saknar det
3. Dokumentera mappning i modulens README
### Fas 2 (inför CRM)
1. Skapa dedikerade tabeller för Organization och Person i `core`-schema
2. Ekonomimodulen FK:ar till `core.organizations` och `core.persons`
### Fas 3 (plattformsnivå)
1. GraphQL/REST-lager för kanoniska objekt
2. Unified search-index över alla objekt
3. AI-kontext-builder som hämtar kanonisk representation
---
## Öppna frågor
1. **Ska kanoniska objekt bo i ett delat `core`-schema eller replikeras per modul?**
Förslag: Shared schema för Person, Organization, Location. Replika med CDC för moduler som behöver hög läsprestanda.
2. **UUID v7 vs ULID?**
UUID v7 är nu RFC-standard och stöds bättre i PostgreSQL-ekosystemet. ULID är alternativ om befintlig kod är svår att migrera.
3. **`metadata`-schemavalidering?**
Modul-specifika JSON Schema-fragment bör definieras och valideras vid INSERT/UPDATE.
4. **Event-schema-registret?**
Hermes bör ha ett schema-register som validerar event-payloads mot `event_version`. Se ARC-002.