# AIA Architecture Overview

Key architectural decisions and rationale for future sessions.

## Positioning (non-negotiable)

**AIA is a *bokføringsverktøy* / accounting software.** Not a `regnskapsforetak`, not a concierge service.

- Customer remains legally responsible for their bookkeeping and filings
- AI proposes; deterministic validation checks; customer approves; system posts
- Never: `AI → we manually review each customer → submit`
- This distinction is what makes AIA scale to thousands vs. becoming a 50-client shop

Legal ground: In Norway, `regnskapsfører` is NEVER legally required. Only `revisor` (auditor) is required, and only above 7M NOK revenue + 27M NOK balance + 10 employees — around 85% of Norwegian companies are below this. This means AI-based bookkeeping is legal for the vast majority of the market.

## Multi-tenant hybrid model

Users → Organizations (many-to-many via `organization_user`) → Companies (one-to-many).

- **User**: a person with credentials
- **Organization**: a billing/team boundary (a bookkeeper's practice, a holding group, a family, or a single-person shop)
- **Company**: a legally-separate entity (has `organisasjonsnummer`, files its own MVA-melding and årsregnskap)
- **Access**:
  - `organization_user`: user is a member of an organization (org-role: admin, member)
  - `company_user`: user has access to a specific company (primary_role: owner, accountant, viewer)
  - Being in an organization does NOT auto-grant company access — access is explicit per-company

## 5-layer tenant isolation

Everything in Core carries `organization_id`. Every layer enforces it, so a bug in one doesn't leak.

| Layer | Where | What |
|-------|-------|------|
| L1 | HTTP middleware (`ResolveTenantFromSession`, `RequireOrganizationContext`, `RequireCompanyAccess`) | Set tenant context from session; block requests without one |
| L2 | Model `creating` event on `BelongsToOrganization` trait | Auto-fill `organization_id` from `TenantContext` on create |
| L3 | Global scope `TenantScope` | Every query adds `WHERE organization_id = <current>`; returns `WHERE 1=0` when no context |
| L4 | Model `saving`, `updating`, `deleting` events | Reject any write where `organization_id` differs from `TenantContext` |
| L5 | Database composite foreign keys `(organization_id, id)` on parent, `(organization_id, child_id) → parent(organization_id, id)` on child | Raw SQL cannot escape tenancy — the DB itself rejects cross-tenant references |

## ULID everywhere

Every model uses ULIDs (26 characters, Crockford base32) as primary keys.

- Sortable by creation time (unlike UUIDs)
- URL-safe, no special characters
- Not exposed as `auto_increment` (no leak of business volume)
- Model trait `HasUlids` handles generation

## Immutable ledger

`journal_entries` and `journal_lines` follow strict double-entry rules:

- `status: draft` → editable (only if unposted)
- `status: posted` → immutable, gets a sequential voucher number `YYYY-NNNNNN` per (company, fiscal_year)
- `status: reversed` → cannot be modified; only reversal entries can be created
- Posted entries CANNOT be deleted via Eloquent (model `deleting` event throws)
- Amounts stored in `øre` (integer) — no floating-point money bugs

## Hash-chained audit log

Every state change goes through `AuditChain::record()`:

- Each event stores `prev_hash` (previous event's `hash`) and computes its own `hash = SHA256(prev_hash + payload)`
- `verifyChain($orgId)` walks the chain end-to-end; returns the ID of the first broken event or `null` if intact
- Events cannot be updated or deleted via Eloquent
- Bookføringsloven § 11 compliance: complete, tamper-evident audit trail retained 5+ years

## AI architecture (Sprint 2+)

The mandatory flow:

```
AI OCR/inference
    ↓
Deterministic validator (rule-based, testable)
    ↓
Customer sees suggestion + confidence score
    ↓
Customer approves (audit-logged)
    ↓
System posts/submits
```

Never `AI → human at AIA reviews → submit`. This is the difference between software and a bookkeeping shop.

Confidence scoring on every AI output. Anything below 0.85 shown to customer as "needs review".

## Norwegian compliance timeline

- **Now**: SAF-T v1.30 on demand, EHF for public sector
- **Jan 2027**: SAF-T v1.40 mandatory; EHF/Peppol mandatory B2B
- **Jan 2030**: Full digital accounting with automated booking mandatory

AIA is being built assuming the 2027-2030 rules — legacy vendors will need to refactor, we start clean.

## Adapter pluggability

Fiken, Tripletex, SAF-T, CSV are pluggable **adapters** in `app/AIA/Adapters/`. They write INTO the canonical ledger via the Migration Center pipeline.

Rule enforced by `NoFikenDependencyTest`: Core, Tenancy, Tax, and Migration modules must NEVER import anything from Adapters. Adapters depend on Core, never the reverse.

Every canonical model has `source_system` (string) + `source_reference` (string) — generic discriminators, never `fiken_id` or similar.

## Sprint plan (agreed)

1. **Sprint 1** (this): Foundation — multi-tenant, ledger, audit, HTTP API, tests
2. **Sprint 2**: Fiken Adapter + Migration/Canonical Ledger stabilization
3. **Sprint 3**: AI OCR for receipts + Bank feed (PSD2 lite)
4. **Sprint 4**: MVA engine + Skatteetaten submission via Maskinporten
5. **Sprint 5**: A-melding + SAF-T v1.40 export
6. **Sprint 6**: Self-service onboarding (BankID) + Stripe billing → transition to C (SaaS)

**Rule**: Never start Sprint N until Sprint N-1 tests pass locally.
