# 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-17. ## 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. ## Pending decisions - Link attachments after posting (API already supports `attachment.put {voucher_id}`): proposed TUI actions — `Ctrl+F` in the voucher detail to upload+link, and selecting an inbox item and pressing a key to link it to a voucher picked from a list. Waiting for a go-ahead. - Priority between **eSKD moms filing** and **bokslut/K2+SRU** for the next backend milestone (eSKD was suggested first). - Moms `report_rules` seed is a reviewed starter mapping only; must be checked against the current Skatteverket blankett before filing. ## Backlog (prioritized, from COMPLIANCE.md §10 and the audit) 1. eSKD file generation for momsdeklaration. 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` (documented, not implemented). 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). `Ctrl+N/C/F` are single bytes and reliable. ## 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 2; forward migrations are in `db.c`.