# bokf — project state Snapshot for resuming work in a new session. Read with `AGENTS.md` (rules) and `docs/TUI-GUIDELINES.md` (UI conventions). Dated 2026-09-18. ## Status Working self-hosted bookkeeping system, not production-proven. Backend ledger core is complete; filing/year-end/payroll are not. TUI is usable and exercised via pty smoke tests; the test suite (`make test`) covers the server/protocol/ledger only. ## Locked decisions 1. **Name/license**: `bokf`, daemon `bokfd`, clients `bokfctl` (scriptable) and `bokftui` (ncurses); GPL-3.0-or-later; repo `~/work/bokf`. 2. **Stack**: C11, Makefile, vendored SQLite 3.53.4 / yyjson 0.13.0 / Argon2 20190702 / SHA-256 (public domain). Only system dep: libncursesw. 3. **Storage**: SQLite WAL, `synchronous=FULL`, STRICT tables, composite-key tenant isolation, append-only triggers, `VACUUM INTO` snapshots. Postgres deliberately rejected for now; keep DB access behind one layer for a later port. 4. **Protocol**: NDJSON over Unix socket (+ optional plain TCP and a native TLS listener), protocol v1. `dry_run` on every mutation, `client_ref` idempotency, stable error codes, `describe` + `agent.instructions`, money in integer öre. 5. **Auth**: multi-org; memberships owner/bookkeeper/viewer; API tokens bound to user+org with scopes, shown once, revocable; sessions in memory; Argon2id. Server messages English, UI Swedish. 6. **Compliance design**: SHA-256 audit chain, SHA-256 voucher chain (canonical encoding in `SCHEMA.md` §7.1/§9.1), period locks, fiscal year close, corrections only as ändringsverifikat, SIE 4 (CP437) in/out. 7. **Verifikat ids**: series is free text (`A`, `V-`, `A ` …); unbroken numbering per fiscal year+series; id displayed as `series+number` (`V-8`). Org setting `default_series` (Inställningar) for new vouchers and new templates. 8. **Templates** (`konteringsmallar`): server-side; formula language over `x` with `+ - * /` and parentheses, positive=debit, negative=credit, zero rows dropped; rounding remainder assigned to the largest row; `{x}` in the description; `template.*` commands + `voucher.post {template,x}`; archive instead of delete. 9. **Ingående balans**: one series `IB` voucher per fiscal year (dated at year start). Reports treat series IB as IB, not period movement; SIE export/import round-trips without double counting; TUI editor shows the year's *effective* opening balances (carry-forward plus IB vouchers) and posts deltas, so nothing is ever edited and a target balance can be entered directly. 10. **Attachments**: content stored in the DB (BLOB), immutable, linked via append-only `voucher_attachments`. Default limit 10 MiB (`max_attachment_bytes`); the socket line limit is derived from it (base64). TUI `Ctrl+F` attaches via file browser; the client checks the size from `meta` first. Setting `attachment_dir` (tilde expanded). 11. **UI keys**: Ctrl+N = add, F5 = refresh, Esc/q = back (never exits), Ctrl+C = quit, 1–9/g = jump, F7 = clear row in editors, F9 = save. Shared line editor and date field; see TUI-GUIDELINES.md. 12. **Login**: two steps — credentials, then org picker ("Välj organisation att representera"). No org switch in the dashboard; fiscal year switch is on the dashboard. `--org ID` bypasses the picker. 13. **Settings**: `settings.get`/`settings.set`; keys `default_series`, `attachment_dir`. 14. **Transport**: Unix socket for host clients; plain TCP loopback-only; native TLS listener (`BOKFD_TLS`, `BOKFD_TLS_CERT/KEY`, OpenSSL, TLS 1.2+, cert reload on file change) with client targets `tls:host:port` and system-trust verification (`BOKFD_TLS_CA` for private CAs). Certificates come from a lego sidecar using INWX DNS-01 (`compose.yaml`). Externals get accounts/roles/tokens, never VPN access. `scripts/deploy.sh` builds locally and ships over SSH, or builds on the host when architectures differ. `scripts/deploy.sh --dev` cross-compiles the binaries here and hot-reloads the daemon (SIGHUP re-exec via `docker cp`), skipping the image build and container recreate. 15. **Reports in the TUI**: rendered as fixed-width Swedish tables that mirror the Kapitas PDF exports (Saldobalans, Resultatrapport with previous-year column and 89xx bokfört/ej bokfört, Balansrapport with Ing balans/Ing saldo/Period/Utg balans and Beräknat resultat, Momsrapport ruta för ruta). The TUI never shows report JSON. Amounts are Swedish formatted (`1 234,56`); moms rutas are whole kronor truncated like Kapitas. 16. **Moms rules (schema v3)**: default seed covers 05 over 3000-3019, 3100-3199, 3300-3399, reverse-charge sales (32xx) in 41, EU purchases in 20/21, reverse-charge output VAT 2614/2624/2634 in 30/31/32, and box 48 signed negative. Rules may share a box and are summed; box 49 is the sum of the moms boxes only. v3 migrates existing databases. ## Pending decisions - Attachments are complete: download (voucher detail `f`, Underlag `Enter`, SHA-256 verified, text inline), attach to an existing voucher (`^F`), remove a link (`d` in the `f` picker) and link an inbox item to a voucher (`k`). - Next backend milestone: **bokslut automation** vs **K2/SRU** (eSKD is done: `report.vat_eskd` + `s = spara eSKD` in the momsrapport). - Moms `report_rules` seed is a corrected starter mapping (schema v3), but there is still no command/TUI to edit rules per org; add one before filing if the mapping needs adjustments (SCHEMA.md §10 promises owner editing). ## Backlog (prioritized, from COMPLIANCE.md §10 and the audit) 1. ~~eSKD file generation for momsdeklaration.~~ `report.vat_eskd` (eSKDUpload 6.0, ISO-8859-1, whole kronor) with a save action in the TUI momsrapport. 2. Bokslut automation (avskrivningar, periodiseringsfond, skatt, resultatdisposition). 3. K2 årsredovisning document + SRU files (INK2/INK2R/INK2S). 4. `audit.verify` must also verify the **voucher** hash chain (today only the audit chain is verified). 5. ~~`report.general_ledger` and `report.voucher_list`~~ implemented (Huvudbok, Verifikationslista) with Kapitas-style TUI tables; the ledger API supports `accounts`/`from`/`to`, the list an optional `series`. 6. `describe` argument schemas (currently name/summary/permission only). 7. Pre-migration `VACUUM INTO` snapshot (promised in SCHEMA.md, not built). 8. ~~Docker image + compose (multi-arch amd64/arm64, GHCR) and systemd unit.~~ Done as a Dockerfile + `compose.yaml` (amd64/arm64 build stage) and `scripts/deploy.sh` over SSH; no registry and no systemd unit (the container is the unit). 9. Password change, user disable, TOTP. 10. Bank import/reconciliation (CSV first, then PSD2), invoicing/reskontra, AGI/payroll if employees. 11. ~~SIE import only into an empty fiscal year; consider broader import.~~ Chronological multi-year import works (CRLF, `#RAR 0`, zero rows, `#IB` rule handled); each year must still target an empty fiscal year. Note: years whose source system kept corrected `#IB`/`#UB` that the vouchers do not reproduce (like the Kapitas 2022-2026 books) diverge from the source when imported as history; the latest year imports exactly. 12. TUI polish: horizontal scrolling in long text fields, bracketed paste. ## Environment / how to run - Demo: db `~/bokf-demo/bokfd.db`, socket `~/bokf-demo/bokfd.sock`, pid file `~/bokf-demo/bokfd.pid`; login `admin` / `demo1234`. Start TUI: `cd ~/work/bokf && BOKFD_SOCKET=$HOME/bokf-demo/bokfd.sock \ BOKFD_USER=admin BOKFD_PASSWORD=demo1234 ./build/bokftui` - Restart daemon: kill the pid file's process, then `BOKFD_BACKUP_DIR=$HOME/bokf-demo/backup \ BOKFD_EXPORT_DIR=$HOME/bokf-demo/export setsid nohup \ ./build/bokfd --db $HOME/bokf-demo/bokfd.db \ --socket $HOME/bokf-demo/bokfd.sock > $HOME/bokf-demo/daemon.log 2>&1 &` - The user's own early instance was `/tmp/x.db` + `/tmp/bokfd.sock` (schema v1, old binary) — recreate or migrate it with the current build if it is still wanted. - TUI smoke tests: drive over a pty with `script -qec`; function-key escape sequences are timing-sensitive there (not an app bug). Arrows arrive as application-mode sequences (`ESC O B` for Down), not `ESC [ B`, because curses enables the keypad. `Ctrl+N/C/F` are single bytes and reliable. Always wrap the run in `scripts/tui-sandbox.sh -- ./build/bokftui ...`: it isolates `XDG_CONFIG_HOME`/`XDG_CACHE_HOME` so a test can never overwrite the real `~/.config/bokf/tui.conf` or `~/.cache/bokf/tui.log`. ## Known caveats - Never commit unless the human asks. - SQLite files must not be backed up live with restic; use `backup.snapshot` (`VACUUM INTO`) and point restic at the snapshots. - Schema version is 3; forward migrations are in `db.c`.