docs: correct stale product facts (arkivplan, architecture, agents, self-hosting, extensions, database map) (#1931)

Every statement was verified against the code on main before editing; the
docs had drifted from the product in ways a customer or agent would act on.

- public/docs/arkivplan-mall.md: product named erp-base; magic-link login;
  BAS 2025/2026; eu-central/eu-west region; US subprocessors for AI. Now
  Accounted, e-mail + password + TOTP (BankID optional), BAS 2026,
  eu-north-1 Stockholm, Bedrock in EU with Resend as the only US
  subprocessor; adds rättelselogg, Peppol inbound, skattekonto imports and
  the säkerhetsbackup ZIP to the räkenskapsinformation tables.
- ARCHITECTURE.md: adds the inline-rättelse correction path, OAuth 2.1 and
  lazy MCP auth, accounted-mcp and claude-plugin, 150+ tools.
- AGENTS.md: defers to CLAUDE.md instead of a drifted copy; keeps the
  Codex-only constraints with the Supabase project name fixed (erp-base).
- README.md: drops LangChain/OpenAI (not dependencies), Node 20/22 facts,
  150+ tools, adds betalfil, Peppol, skattekonto and the Claude plugin.
- docs/PEPPOL_FOUNDATION.md: the two sentences denying network delivery
  and inbound support now describe the live Qvalia path.
- docs/SELF-HOSTING.md, docs/DOCKER.md: clone URLs and directory names,
  Sentry DSNs are not read by the app, image pinning uses the 7-char SHA
  tags the workflow actually publishes (no semver tag has been cut).
- docs/EXTENSIONS.md: replaces the fictional sector tree with the 19 real
  extensions/general directories; lib/reports/sru-encoding.ts.
- .claude/rules/database.md: 680+ migrations, ~170 live tables, adds the
  tables and RPCs that matter since July, drops sandbox_users.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-26 13:36:05 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 188816652d
commit 9396e54965
10 changed files with 179 additions and 253 deletions
+14 -12
View File
@@ -17,15 +17,15 @@ You do **not** need Node.js, npm, or anything else installed locally. The pre-bu
mkdir Accounted && cd Accounted
# Compose file + env template
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/.env.docker.example
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/.env.docker.example
# Cron sidecar (Dockerfile + schedule)
mkdir -p docker
curl -fsSL -o docker/cron.Dockerfile \
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/cron.Dockerfile
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/cron.Dockerfile
curl -fsSL -o docker/crontab.self-hosted \
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/crontab.self-hosted
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/crontab.self-hosted
```
### 2. Configure your environment
@@ -146,10 +146,10 @@ NEXT_PUBLIC_APP_URL=https://gnubok.example.com
### 3. Download the overlay + Caddyfile
```bash
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/docker-compose.caddy.yml
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/docker-compose.caddy.yml
mkdir -p docker
curl -fsSL -o docker/Caddyfile \
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/Caddyfile
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/Caddyfile
```
### 4. Start with the overlay
@@ -216,19 +216,21 @@ No env vars needed: always available.
## Updating
The default `IMAGE_TAG=latest` follows `main` and updates on every `docker compose pull`. For production, **pin to a specific release** so updates are deliberate:
The default `IMAGE_TAG=latest` follows `main` and updates on every `docker compose pull`. For production, **pin to a specific build** so updates are deliberate. Every merge to `main` publishes the image under two tags: `latest` and the bare 7-character commit SHA (for example `3e4b5dd`), so a pin looks like:
```env
# .env
IMAGE_TAG=1.2.3
IMAGE_TAG=3e4b5dd
```
Browse available tags at https://github.com/erp-mafia/gnubok/pkgs/container/gnubok. For maximum integrity, pin by digest:
Browse available tags at https://github.com/erp-mafia/accounted/pkgs/container/gnubok (the image name keeps the historical `gnubok` package name on purpose). For maximum integrity, pin by digest; the digest is printed in the `docker-publish` workflow run and by `docker buildx imagetools inspect ghcr.io/erp-mafia/gnubok:<sha>`:
```env
IMAGE_TAG=1.2.3@sha256:abcdef...
IMAGE_TAG=3e4b5dd@sha256:abcdef...
```
Semver tags (`1.2.3`, `1.2`, `1`) are published only when a `v*.*.*` git tag is cut. No such tag exists yet, so until the first tagged release the commit SHA is the only immutable pin.
Apply updates:
```bash
@@ -246,8 +248,8 @@ If you prefer to build locally instead of pulling the pre-built image:
```bash
# Clone the repo
git clone https://github.com/gnubok/gnubok.git
cd Accounted
git clone https://github.com/erp-mafia/accounted.git
cd accounted
cp .env.docker.example .env
# Fill in .env
+58 -89
View File
@@ -42,12 +42,14 @@ These are configured via `extensions.config.json` and only loaded when explicitl
Sector extensions are tied to a specific market sector. They're only relevant to businesses operating in that sector. A restaurant owner wants "Food Cost %" but an IT consultant does not.
Examples:
Examples of what a sector extension could be (design intent, none of these exist):
- **Restaurant:** Food Cost %, Earnings Per Alcohol Liter, POS Z-Report Import, Tip Tracking
- **Construction:** ROT Calculator, Project Cost Tracking
- **Hotel:** RevPAR, Occupancy Tracking
- **IT/Consulting:** Billable Hours Ratio, Project Billing Metrics
- **E-commerce:** Shopify Order Import, Multi-channel Revenue Analytics
- **E-commerce:** Multi-channel Revenue Analytics
**Current state (2026-08-26):** no sector extension has been built. Every shipped extension lives under `extensions/general/`, and `SectorSlug` in `lib/extensions/types.ts` is currently just `'general'`. The sector concept, the marketplace routes (`app/(dashboard)/extensions/[sector]/...`) and the `sector/slug` extension-id format are in place for when the first one arrives. Shopify and WooCommerce order import shipped as general extensions, not e-commerce sector ones.
### The Unified Model
@@ -55,39 +57,30 @@ Both general and sector extensions live in the same system:
```
extensions/
general/ ← General extensions (any business)
document-extraction/
invoice-inbox/
mcp-server/
skatteverket/
push-notifications/
enable-banking/
calendar/
email/
restaurant/ ← Restaurant sector extensions
food-cost/
earnings-per-liter/
pos-import/
tip-tracking/
construction/ ← Construction sector extensions
rot-calculator/
project-cost/
hotel/ ← Hotel sector extensions
revpar/
occupancy/
tech/ ← IT/Consulting sector extensions
billable-hours/
project-billing/
ecommerce/ ← E-commerce sector extensions
shopify-import/
multichannel-revenue/
export/ ← Export & international trade extensions
eu-sales-list/
intrastat/
vat-monitor/
currency-receivables/
general/ ← General extensions (any business); the only sector today
arcim-migration/ ← Systemmigration: import from Fortnox, Visma, Bokio, Björn Lundén, Briox
bolagsverket/ ← Digital årsredovisning filing to Bolagsverket (iXBRL)
calendar/ ← Kalender: month/week/day views
cloud-backup/ ← Molnsynkronisering: sync the säkerhetsbackup to the user's own cloud storage
document-extraction/ ← AI-extrahering av underlag: reads receipts and invoices, fills supplier/amount/VAT/date
email/ ← E-post (Resend): invoices and reminders by e-mail
enable-banking/ ← Bankintegration (PSD2): automatic bank transaction sync
invoice-inbox/ ← Dokumentinkorg: forward supplier invoices to a unique address
mail/ ← Brevlådor: lets Kvittojakten (receipt hunt) search the user's mailboxes
mcp-server/ ← MCP-server (API): bookkeeping via Claude, Cursor or any MCP client
push-notifications/ ← Push-notiser: event notifications
shopify/ ← Shopify: paid orders and refunds into the Ordersidan
skatteverket/ ← Skatteverket: VAT declaration submission via BankID, skattekonto
stripe/ ← Stripe-betalningar: payment links on invoices, automatic avprickning
tic/ ← Bolagsuppgifter: company data from public registers via TIC
whatsapp-inbox/ ← WhatsApp-inkorg: receipts as photo or PDF to Accounted's WhatsApp number
woocommerce/ ← WooCommerce: paid orders and refunds into the transaction inbox
_example-branding/ ← Whitelabel branding starter (disabled by default)
example-logger/ ← Minimal example extension (index.ts only, no manifest)
```
Which of these are compiled in is decided by `extensions.config.json`; today that is everything except `bolagsverket`, `push-notifications`, `_example-branding` and `example-logger`.
Each extension directory contains a `manifest.json` declaring metadata, entry point, workspace component path, required env vars, and npm dependencies.
In the marketplace:
@@ -283,8 +276,8 @@ type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
| Earnings Per Alcohol Liter | Restaurant | A + B (both) | Liters sold per day/week | Alcohol revenue from BAS 3001 | Calculates revenue/liter, trends over time |
| Food Cost % | Restaurant | A (core) | None | Food purchases (4000-series), food revenue (3000-series) | Calculates food_cost/food_revenue %, trends |
| Tip Tracking | Restaurant | B (manual) | Tip amounts per shift | Optionally reads staff cost accounts | Total tips, tips/employee, tip % of revenue |
| POS Z-Report Import | Restaurant | B (manual) | Uploads Z-report CSV/Excel | None | Parses POS data, stores in extension, shows daily sales analytics |
| Shopify Order Import | E-commerce | B (manual) | Uploads order export | None | Imports orders into extension, shows revenue by product, trends |
| POS Z-Report Import (not built) | Restaurant | B (manual) | Uploads Z-report CSV/Excel | None | Parses POS data, stores in extension, shows daily sales analytics |
| Shopify / WooCommerce (shipped, general) | General | B (manual) | Store connection | None | Pulls paid orders and refunds into `webshop_orders` for the order and transaction inboxes |
| ROT Calculator | Construction | A + B (both) | Labor hours, material costs per job | Invoice data for customer billing | ROT deduction amounts (30% of labor, max 50k/year per customer) |
| RevPAR | Hotel | A + B (both) | Room count and occupancy | Room revenue accounts | Revenue Per Available Room, occupancy rate |
| Billable Hours Ratio | IT/Consulting | A + B (both) | Hours worked per project | Invoice data for billed amounts | Billable/total hours, effective hourly rate |
@@ -297,7 +290,7 @@ type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
```
extensions/ ← Extension source code (opt-in via config)
general/ ← General extensions
general/ ← General extensions (the only sector that exists)
invoice-inbox/
manifest.json ← Metadata, entry point, env vars, workspace path
index.ts ← Extension definition + logic (exports Extension)
@@ -313,11 +306,14 @@ extensions/ ← Extension source code (opt-in via conf
push-notifications/
manifest.json
index.ts
lib/
api-routes.ts
notification-scheduler.ts
notification-sender.ts
enable-banking/
manifest.json
index.ts
lib/
components/
email/ ← Email service extension (registers Resend impl)
manifest.json
index.ts
@@ -325,45 +321,22 @@ extensions/ ← Extension source code (opt-in via conf
calendar/
manifest.json
index.ts
restaurant/ ← Restaurant sector
food-cost/
manifest.json
earnings-per-liter/
manifest.json
pos-import/
manifest.json
tip-tracking/
manifest.json
construction/ ← Construction sector
rot-calculator/
manifest.json
project-cost/
manifest.json
hotel/ ← Hotel sector
revpar/
manifest.json
occupancy/
manifest.json
tech/ ← IT/Consulting sector
billable-hours/
manifest.json
project-billing/
manifest.json
ecommerce/ ← E-commerce sector
shopify-import/
manifest.json
multichannel-revenue/
manifest.json
export/ ← Export & international trade sector
eu-sales-list/
components/
mcp-server/ ← MCP server: server.ts, tools, prompts, resources, skills, widgets
manifest.json
index.ts
intrastat/
manifest.json
vat-monitor/
manifest.json
currency-receivables/
manifest.json
server.ts
arcim-migration/ ← Provider migration (Fortnox, Visma, Bokio, BL, Briox)
bolagsverket/ ← Digital årsredovisning (iXBRL)
cloud-backup/ ← Cloud sync of the säkerhetsbackup
mail/ ← Mailbox connections for Kvittojakten
shopify/ ← Shopify order import (api-routes.ts, components/, lib/)
woocommerce/ ← WooCommerce order import (api-routes.ts, components/, lib/)
stripe/ ← Stripe payment links and avprickning
tic/ ← Company data lookup (TIC)
whatsapp-inbox/ ← WhatsApp receipt intake
_example-branding/ ← Whitelabel starter, disabled by default
example-logger/ ← Minimal example, index.ts only
extensions.config.json ← Which extensions are enabled (empty = core-only)
extensions.schema.json ← JSON Schema for extensions.config.json
@@ -384,7 +357,7 @@ lib/
email/
service.ts ← EmailService interface + no-op default + getEmailService()
reports/
sru-export/ ← SRU file export (core, not an extension)
sru-encoding.ts ← SRU file encoding (core, not an extension)
ne-bilaga/ ← NE tax form attachment (core, not an extension)
scripts/
@@ -403,22 +376,18 @@ components/
DateRangeFilter.tsx
EmptyExtensionState.tsx
ExtensionLoadingSkeleton.tsx
general/ ← General extension workspaces
general/ ← General extension workspaces (the only ones that exist)
ReceiptOcrWorkspace.tsx
AiCategorizationWorkspace.tsx
AiChatWorkspace.tsx
restaurant/ ← Restaurant extension workspaces
EarningsPerLiterWorkspace.tsx
FoodCostWorkspace.tsx
PosImportWorkspace.tsx
construction/
RotCalculatorWorkspace.tsx
hotel/
RevparWorkspace.tsx
tech/
BillableHoursWorkspace.tsx
ecommerce/
ShopifyImportWorkspace.tsx
InvoiceInboxWorkspace.tsx
EnableBankingWorkspace.tsx
CalendarWorkspace.tsx
CloudBackupWorkspace.tsx
ArcimMigrationWorkspace.tsx
PushNotificationsWorkspace.tsx
TicWorkspace.tsx
MailConnectionsPanel.tsx
WhatsAppLinkPanel.tsx
app/(dashboard)/
extensions/ ← Marketplace
+2 -2
View File
@@ -5,7 +5,7 @@ Issue #546 requires two separable capabilities:
1. Produce a correctly structured Peppol BIS Billing 3 invoice from Accounted data.
2. Deliver and receive documents through the Peppol network.
The first slice implemented the invoice profile. The second slice adds an immutable staged-delivery and audit foundation. Neither slice claims network delivery.
The first slice implemented the invoice profile. The second slice added an immutable staged-delivery and audit foundation. Neither of those two slices claimed network delivery; since 2026-08-21 both sending and receiving run over the Peppol network through Qvalia (see the Qvalia and Receiving (PR2) sections below). The sections up to "Access point: Qvalia" describe the foundation those slices left behind and the requirements the Qvalia work had to meet.
## Implemented scope
@@ -74,7 +74,7 @@ No invoice status should change merely because XML was generated or accepted by
### Receiving
Inbound invoices are a separate acceptance slice. It requires provider webhook authentication, raw XML retention, duplicate detection, supplier matching, safe attachment handling, and mapping into the supplier-invoice inbox without treating received content as trusted. Nothing in this foundation claims inbound support.
Inbound invoices are a separate acceptance slice. It requires provider webhook authentication, raw XML retention, duplicate detection, supplier matching, safe attachment handling, and mapping into the supplier-invoice inbox without treating received content as trusted. The original foundation did not claim inbound support; that slice has since shipped (`peppol_registrations`, `peppol_inbound_documents`, the inbound cron and inbox delivery) and is described under "Receiving (PR2)" below.
### UI and API
+7 -12
View File
@@ -65,8 +65,8 @@ These are all available on Supabase hosted. `pg_cron` requires a paid plan: if y
**Option A: Setup script (recommended):**
```bash
git clone https://github.com/erp-mafia/gnubok.git
cd Accounted
git clone https://github.com/erp-mafia/accounted.git
cd accounted
./setup.sh
```
@@ -75,8 +75,8 @@ The script checks prerequisites, prompts for your Supabase credentials, auto-gen
**Option B: Manual:**
```bash
git clone https://github.com/erp-mafia/gnubok.git
cd Accounted
git clone https://github.com/erp-mafia/accounted.git
cd accounted
cp .env.docker.example .env
```
@@ -313,14 +313,9 @@ VAPID_SUBJECT=mailto:you@example.com
Generate VAPID keys with `npx web-push generate-vapid-keys`. Push notifications require HTTPS.
### Error Tracking (Sentry)
### Error Tracking
```bash
SENTRY_DSN=https://...@sentry.io/...
NEXT_PUBLIC_SENTRY_DSN=https://...@sentry.io/...
```
Sentry is disabled if these are not set. No errors are thrown.
There is no Sentry integration. `SENTRY_DSN` and `NEXT_PUBLIC_SENTRY_DSN` are not read by the app: setting them changes nothing. Error-level events go to the container logs (structured JSON on stdout/stderr); `lib/observability/sink.ts` is a provider-agnostic seam that stays a no-op until an adapter is registered with `registerObservabilitySink()`, so a self-hosted build carries no third-party error-tracking dependency. If you want alerting, ship the container logs to your log system and alert there. See [docs/security/logging-and-observability.md](security/logging-and-observability.md).
## Storage Buckets
@@ -341,7 +336,7 @@ If a new release includes database migrations, apply them before restarting:
supabase db push
```
Check the [release notes](https://github.com/erp-mafia/gnubok/releases) for migration instructions.
Check the [release notes](https://github.com/erp-mafia/accounted/releases) for migration instructions.
## Architecture Overview