Commit Graph
17 Commits
Author SHA1 Message Date
MattssonandClaude Opus 4.7 5725c25bf1 Logs/improved logging (#398)
* feat(mcp): add create_transactions tool with /pending approval gate

New MCP tool gnubok_create_transactions stages 1–10 transactions per call
as pending_operations of type create_transaction (risk: medium). Each item
becomes its own card on /pending; on confirm, the executor inserts the row
into transactions with import_source='mcp' so MCP-staged ingestion is
distinguishable from PSD2 sync. Designed for skill workflows that pull
external data (e.g., Airtable) and want the user to gate the writes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(bas): strip concatenated group headers from corrupted account names

A chart-data import bug had glued the next group's header onto the last
account in each preceding group across all eight bas-data class files
(e.g. account 2670 read "Utgående moms på försäljning inom EU, OSS 27
PERSONALENS SKATTER, AVGIFTER OCH LÖNEAVDRAG"). The corrupted names
surface in transaction dropdowns, ledgers, SIE exports and årsredovisning,
and risk VAT miscategorization on the OSS (2670) and blandad-verksamhet
(6999) accounts specifically.

- Cleans 69 account_name and 64 description fields across class-1..8 files
- Adds a regression test asserting no name contains a concatenated header
- Ships an idempotent safety-net migration that updates already-seeded
  chart_of_accounts rows, gated on the corrupted string so user
  customizations are preserved

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(errors): add structured error codes and handling for various operations

- Introduced a new structured error registry in `structured-errors.ts` to standardize error handling across the application.
- Added Swedish and English messages for various error scenarios, including validation, authorization, and bookkeeping errors.
- Implemented a client-side error toast in `use-error-toast.ts` to display user-friendly error messages with remediation hints.
- Created a wrapper for recording operation outcomes in `record-operation.ts`, enhancing audit capabilities for operations.
- Developed a provider call wrapper in `with-provider-call.ts` to handle external HTTP calls with structured logging and error mapping.
- Added a new SQL migration to extend the processing history with new event types and aggregate types for better operational telemetry.

* Refactor supplier API routes to use context-based logging and error handling

- Replaced direct Supabase client usage in GET and POST routes with context-based approach using `withRouteContext`.
- Enhanced error handling to provide structured error responses for supplier creation and listing.
- Updated logging to include request IDs for better traceability.
- Introduced new error codes for supplier-related operations.
- Refactored tax deadlines cron job to utilize context and improved error handling.
- Updated ESLint configuration to enforce logging practices across API and lib directories.
- Enhanced arcim migration extension with structured error handling and logging.
- Added classification for provider errors to improve user-facing error messages.
- Introduced request ID in extension context for better log correlation.

* fix(route-context): update DynamicParams type for improved type safety in route handlers

* feat(transactions): add 'create_transaction' operation to PendingOperationType

* fix(route): ensure companyId is non-nullable in loadAndDeriveAbsence function

* fix(route-context): ensure companyId is always non-null by short-circuiting with COMPANY_CONTEXT_MISSING

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:12:02 +02:00
Jakob WennbergandClaude Opus 4.7 94f15b9c6c feat(invoice-inbox): pin documents to bank transactions + MCP tools (#397)
* feat(invoice-inbox): pin documents to bank transactions + MCP tools

Adds a first-class flow for attaching unmatched inbox documents to bank
transactions, separate from the existing supplier-invoice convert path:

- new transactions.document_id FK → document_attachments (ON DELETE SET NULL)
- POST/DELETE /api/transactions/[id]/attach-document
- categorize route propagates the link to journal_entry_id on commit
- three new MCP tools: gnubok_list_unmatched_documents,
  gnubok_get_document_content (5-min signed URL),
  gnubok_attach_document_to_transaction (staged via pending_operations)
- InvoiceInboxWorkspace gains a "Koppla till transaktion" picker dialog
  ranked by amount-match, plus a "Bilaga" badge in SwipeCategorizationView
- regex extraction unchanged; supplier-invoice convert flow unchanged

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): address PR #397 review findings

- categorize: destructure { error } from the document-link update so
  Supabase-level failures are logged instead of silently dropped (BFL 5 kap 6 §
  receipt-on-verifikation contract).
- list_unmatched_documents: emit next_cursor whenever the inbox query may have
  more rows, not only when the post-filter slice was full; switch to composite
  (created_at, id) cursor to avoid same-second collisions.
- DELETE /attach-document: return 404 when the tx isn't in the company; return
  409 when the linked document already has journal_entry_id set
  (räkenskapsinformation immutability).
- risk tier: attach_document_to_transaction medium (was low) — link becomes
  part of verifikation underlag once categorize propagates it.
- pg-real test: stop reusing $2 across uuid + text-concat contexts (Postgres
  couldn't deduce the parameter type).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(migrations): break duplicate version 20260505120000 (Supabase Preview)

Two migrations on main share filename version 20260505120000:
  - 20260505120000_api_keys_refresh_token.sql (PR #392)
  - 20260505120000_drop_agent_auto_commit.sql (PR #394)

The schema_migrations primary key is (version), so any fresh DB doing
`supabase db push` over both files conflicts on the second insert. This is
why every PR with a migration since #394 has had Supabase Preview either
fail or skip.

Renaming _drop_agent_auto_commit to 20260505190027 — that matches the
timestamp recorded in prod schema_migrations from when apply_migration was
called for it, so future `db push` against prod sees the file as already-
applied (no re-run). The migration body is fully idempotent (IF EXISTS on
every drop) so a re-run would be a no-op anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): address PR #397 round-2 compliance review

Two BFL gaps the compliance bot flagged on the round-1 fix commit:

1. commitAttachDocumentToTransaction silently broke verifikation→underlag if
   the transaction was categorized between staging and approval. Now reads
   transactions.journal_entry_id at commit time and, if non-null, also writes
   document_attachments.journal_entry_id in the same commit so BFL 5 kap 6 §
   is satisfied regardless of order.

2. Application-layer DELETE check was racy (SELECT then UPDATE) and the FK
   ON DELETE SET NULL path could null transactions.document_id even for a
   document that is räkenskapsinformation. Added a BEFORE UPDATE OF
   document_id trigger on transactions that raises check_violation when the
   previously-attached document has document_attachments.journal_entry_id
   set. The app-layer guard stays for friendly Swedish messaging; the
   trigger is the DB-level safety net.

pg-real test extended to cover both directions of the trigger (block detach
+ block swap) and the happy-path detach when there's no JE link yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): address PR #397 round-3 compliance review

Four findings from the round-2 update of the compliance bot. The first three
are genuine compliance gaps; the fourth (preview metadata distinguishing pre-
vs post-categorization overwrites) is a UX nicety left for follow-up.

1. transactions.document_id FK switched from ON DELETE SET NULL to RESTRICT
   (migration 20260506100000). Removes the "trigger ordering" concern: a doc
   that's pinned to any tx now cannot be deleted at all without explicit
   detach first. Belt-and-braces with block_document_deletion.

2. commitAttachDocumentToTransaction now does:
   - pre-check that mirrors the DELETE route's 409 when the existing pinned
     doc is räkenskapsinformation, so the same Swedish message is returned
     in both paths;
   - UPDATE…RETURNING journal_entry_id so the propagation decision uses the
     post-update state, closing the read-then-write race with concurrent
     categorize. Either ordering of attach-then-categorize or
     categorize-then-attach now lands at the same correct final state.

3. Both DELETE /attach-document and the MCP commit path catch the trigger's
   check_violation (SQLSTATE 23514) and translate to 409 with the Swedish
   underlag message. The trigger remains the DB-level safety net; the app
   layer is responsible only for friendly UX.

pg-real test rewritten for ON DELETE RESTRICT (blocks deletion of pinned doc;
detach-then-delete works). Unit coverage added for commitAttach: 404, two
distinct 409 paths (pre-check + trigger-translation), happy-path
uncategorized, and propagation when tx was categorized between staging and
commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): address PR #397 round-4 compliance review

Three of five round-3 findings actioned:

1. commitAttachDocumentToTransaction: surface propagation failure rather
   than logging-and-continuing. If document_attachments.journal_entry_id
   can't be set after the transaction has been categorized, the op fails
   (status 500) with a Swedish message instructing retry. Retry is
   idempotent — same document_id on the tx, same propagate target.

2. Replace check_violation (23514) matching with a stable
   "BFL_DOCUMENT_IMMUTABILITY:" message prefix. The trigger now uses default
   P0001 + tagged message; both the route handler and the executor match on
   the prefix instead of the generic SQLSTATE. Future unrelated CHECK
   constraints on transactions can no longer accidentally surface as the
   räkenskapsinformation message.

3. gnubok_list_unmatched_documents now returns invoice currency alongside
   amount so an agent can FX-normalise before comparing to
   transactions.amount. Description updated to make the requirement
   explicit. Mirrored in the UI: AttachToTransactionDialog ranks
   same-currency rows by amount distance and pushes cross-currency rows to
   the bottom of the list.

Skipped:
- Two-migration window for FK action change is acknowledged as resolved by
  the bot; deploy-atomicity is an ops concern, not code.
- Period-lock check in attach/detach: realistic compliance concern is
  already covered by the existing immutability trigger (post-categorize) and
  by the engine's period-lock enforcement (categorize itself). A dedicated
  period check on pre-categorize attach would only guard against pinning a
  doc to a tx in a closed period — defensible defense-in-depth, but no
  active BFL violation. Left for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): address PR #397 round-5 compliance review

Three of four findings actioned. The fourth (block_document_deletion
verification) is already covered by 20240101000017_enforcement_triggers.sql
which raises when document_attachments.journal_entry_id IS NOT NULL on a
posted/reversed entry — confirmed via grep, no code change needed.

1. categorize/route.ts: propagation no longer fires-and-forgets. If
   document_attachments.journal_entry_id can't be set after the JE has been
   committed, the response now carries a document_link_warning field with a
   Swedish retry message. The JE is already committed so we can't roll back,
   but the client can no longer mistake a partial attach for a clean
   categorize.

2. Rättelse audit trail (BFL 5 kap 5 §): both the REST POST handler and the
   MCP commit executor now append a TransactionDocumentReplaced event to
   processing_history whenever a non-null document_id is overwritten, with
   previous_document_id and new_document_id in the payload. Best-effort —
   logging failure must not roll back the (compliant) attach. The previous
   doc id is also returned in the response so callers see what was displaced.

3. MCP staging preview now exposes the existing doc's identity
   (existing_document_id, existing_document_file_name) plus an explicit
   existing_document_is_rakenskapsinformation flag, so a human approver sees
   "replaces X.pdf with Y.pdf" rather than just a will_overwrite_existing
   boolean. Mirrors BFL 5 kap 5 § informed-rättelse intent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): close trigger SELECT race (PR #397 round-6)

The enforce_transactions_document_immutability trigger SELECTed
document_attachments.journal_entry_id without a row lock. A concurrent
UPDATE setting journal_entry_id on that row could commit between the
trigger's SELECT and its RAISE, letting a detach slip through against a
document that just became räkenskapsinformation.

Add FOR SHARE to the SELECT inside the trigger. A concurrent journal_entry_id
write blocks on our share lock until our transaction commits, so either we
observe the propagation and raise, or we run first and the propagation
observes our committed detach (which is fine because journal_entry_id was
still null at that point).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(invoice-inbox): bidirectional immutability + richer staging preview (PR #397 round-7)

Two of six round-6 findings actioned. The other four are recurring
architectural recommendations (atomic audit-log writes, background
reconciliation jobs, migration consolidation, anti-join via materialized
view) that are properly scoped as follow-up work.

1. document_attachments side of the immutability link (BFL 5 kap 6 § works
   in both directions). New trigger enforce_document_journal_entry_immutability
   blocks UPDATE OF journal_entry_id when going from non-null to NULL or to a
   different uuid. The original null→uuid path (initial propagation in the
   categorize / commitAttach flows) still works. Migration
   20260506130000.

2. gnubok_attach_document_to_transaction staging preview now joins on
   invoice_inbox_items.extracted_data and surfaces vendor/amount/currency/
   invoice_date alongside the existing doc filename/mime metadata. Gives the
   human approver the same hints the agent saw before choosing the
   attachment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 10:24:57 +02:00
Jakob WennbergandClaude Opus 4.7 5c52f24a49 feat(mcp): agent-native improvements — progressive discovery, widgets, skills, telemetry (#393)
* feat(mcp): agent-native improvements — progressive discovery, widgets, skills, telemetry

Four coordinated streams of MCP server improvements that move gnubok toward
agent-first design, grounded in Anthropic's Nov 2025 "Code execution with MCP"
article and the May 2026 MCP conference talk.

Context budget — minimize tools/list payload
- New gnubok_search_tools: progressive discovery with name|summary|full detail
  levels and scope filtering. Agents pull only the schemas they need.
- Trimmed all 50 tool descriptions from multi-paragraph blocks (avg ~500-1000
  chars) to one-sentence summaries (avg ~120 chars). Args/Returns/Examples
  blocks dropped — they duplicated inputSchema.
- outputSchema declared on every tool; structuredContent emitted on every
  successful tools/call (was previously only widget-tagged tools).
- protocolVersion bumped to 2025-06-18 (negotiates back to 2024-11-05).
- Workflow examples consolidated into initialize.instructions.
- Net effect: tools/list payload ~43 KB / ~10.8K tokens for 51+ tools, with
  headroom guard at 20K tokens.

MCP applications — server-shipped UI widgets
- New widgets/ directory with typed UiWidget contract; receipt-matcher moved
  out of widget-html.ts (which was deleted) into widgets/receipt-matcher.ts.
- New gnubok_vat_review_widget tool + interactive momsdeklaration widget
  (all 8 rutor with summary card, theme-aware light/dark, copy buttons).
- resources/list and resources/read iterate uiWidgets dynamically — adding
  the next widget is a single file drop.

Skills over MCP — domain-knowledge primitive
- 5 user-facing SKILL.md-style workflow guides authored from existing
  .claude/skills/swedish-* development skills:
  • month-end-close — book → reconcile → VAT (monthly filers) → lock
  • quarterly-vat-review — ruta-by-ruta map, deadlines, common errors
  • year-end-close — bokslut, bokslutstransaktioner, lock → year-end
    → opening balances → close (irreversible)
  • invoicing-rules — ML 17 kap. 24 §, customer types, ROT/RUT, Peppol
  • payroll-monthly — salary run → calculate → review → AGI XML
- gnubok_list_skills (with optional tag filter) + gnubok_load_skill(slug).
  Both unscoped — available to any authenticated key.
- Each skill also exposed as MCP resource at gnubok://skill/<slug>
  (text/markdown) for forward compatibility with a future native
  skills/list primitive.

Tool-call telemetry — measure before optimizing further
- Three new CoreEvent types (mcp.tool_called, mcp.tools_list_called,
  mcp.resource_read), all persisted to event_log (30-day TTL, RLS-scoped).
- Fire-and-forget emission from the dispatcher — never blocks JSON-RPC
  response, double-guarded against handler failures.
- tools/call instrumented at all four exit points (success, execution
  error, scope denied, unknown tool). Latency measured tightly around
  tool.execute() — excludes dispatcher overhead.
- tools/list logs returned tool count (informs progressive-discovery
  adoption); resources/read logs URI + kind discriminator (widget /
  skill / data / unknown).
- No PII or secret material in payloads — only metadata.

Out of scope (explicitly deferred):
- Code-mode SDK (no production code-mode hosts to consume it yet).
- Elicitations (require streamable HTTP transport — bigger architectural lift).
- DB lockdown / RPC funnel (foundational; should follow once telemetry tells
  us where writes actually flow).
- CRUD → intent endpoints (frontend coupling — multi-PR effort).

Tests: +37 new unit tests across search-tools, output-schema, payload-size,
vat-review-widget, skills, telemetry. Existing receipt-matcher test updated
for the new structuredContent contract. 2,615 unit tests passing.
Production build green. No new lint warnings or errors in changed files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): address PR #393 review findings

Greptile (P1 + P2) and the Swedish accounting compliance bot flagged 9 issues
across security, data correctness, and skill content. All addressed:

Security (P1)
- gnubok_search_tools: scope filter now fails closed when __keyScopes is
  absent. The earlier permissive default leaked the full tool inventory if
  the dispatcher's hard-coded name check ever silently broke. Marker presence
  is part of the contract — explicitly empty array also hides scoped tools.
  Two new test cases pin the fail-closed behaviour.

Compliance — VAT (data correctness)
- get_vat_report now aggregates 2614/2624/2634 (reverse-charge output VAT)
  and exposes them as ruta30/ruta31/ruta32 per SKV 4700. ruta48 also picks
  up 2647 (missing before). ruta49 formula corrected to
  (10+11+12+30+31+32) − 48. The widget renders the new rutor between the
  Utgående and Ingående sections.
- Widget ruta 05 sub-label updated from "3001+3002+3003" to "all momspliktig
  försäljning oavsett skattesats" — ruta 05 covers all domestic taxable
  supplies, not just direct-rate sales.
- Refactored: extracted computeVatReport() helper used by both
  gnubok_get_vat_report and gnubok_vat_review_widget. Removes the
  rename-fragile tools.find() lookup at runtime.

Compliance — payroll
- payroll-monthly skill: replaced "born 1958 or earlier = 10.21%" (the 2024
  formulation) with the statutory rule "age 66+ on 1 January of the income
  year (67+ from income year 2026)". Removed the unsourced "age 16–18:
  11.78%" row in favour of a current växa-stöd description with explicit
  Prop. 2025/26:34 reference and a "verify against current Skatteverket
  tables" caveat.

Compliance — skills text
- invoicing-rules: added explicit footnote on the 1 April 2026 livsmedel
  rate change. Restaurang/servering stays at 12 %; livsmedel sold in other
  forms drops to 6 %. Per Prop. 2025/26:55.
- year-end-close: clarified periodiseringsfond cap as "25 % of överskott
  before this year's avsättning" (IL 30 kap.), removing the ambiguous
  "skattemässigt resultat" phrasing that could be misread as a circular
  after-fond computation.

Schema correctness
- STAGED_OPERATION_SCHEMA.required gains "staged" — every path through
  stagePendingOperation returns the field, so the schema now matches the
  contract that MCP clients validate against.

Tests: 2,617 passing (+2 for the search-tools fail-closed cases).
Production build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): address PR #393 round-2 compliance review

Round 2 of the swedish-compliance bot ran after the previous fix-push and
flagged four substantive items + a recommendation. All addressed:

VAT computation (data correctness)
- ruta05 expanded beyond 3001/3002/3003 to cover the common BAS taxable-
  revenue accounts (3001-3008, 3041-3048, 3051-3058, 3071-3078). Companies
  that book to 30xx alternates were previously under-reporting taxable
  turnover; now all standard BAS taxable-revenue numbering contributes.
- One-sided reverse-charge warning: when output VAT is booked on
  2614/2624/2634 (rutor 30/31/32 > 0) but the matching calculated input
  VAT (2645) is zero, computeVatReport now returns a Swedish-language
  warning string. ruta49 is inflated in this case — the warning surfaces
  the most common reverse-charge error per the swedish-vat skill. The
  widget renders warnings in a terracotta panel above the summary card.
- computeVatReport exported and a focused unit test added — exercises 2647
  inclusion in ruta48, reverse-charge balanced/unbalanced cases, and the
  expanded ruta05 mapping. Fills the gap that prior tools/call integration
  tests couldn't reach.

Skill content
- payroll-monthly Step 6: BAS journal-entry example no longer hard-codes
  31.42 % on the 7510/2730 lines. The avgift line is now described as
  "avgift_base × applicable_rate per employee" with explicit aggregation
  semantics for runs that mix full-rate and reduced-rate employees.
  Aligns with the Step-4 reduced-rate caveats already in place.
- invoicing-rules ROT/RUT block: replaced the bare "30 % / max 50 000 SEK"
  text with the full year-by-year picture — RUT 50 % / 75 000 max, ROT
  baseline 30 % / 50 000 max, 2024 H2 doubled ceiling, 2025 May–Dec
  enhanced 50 % rate. Defaults to "verify against current Skatteverket
  table" rather than a single hard-coded rate.

False positives in the round-2 review (no fix needed; documented for the
record):
- "Old vat_report path still computes ruta48 without 2647" — the old path
  was replaced by computeVatReport in the previous push; the bot was
  reading the diff hunk and conflating it with current behaviour.
- "Widget prose says ruta49 = (10+11+12) - 48" — no such prose exists in
  widgets/vat-review.ts. The skill body has the correct
  (10+11+12+30+31+32)-48 formula.
- "Including 'reversed' status entries in VAT aggregation may over-count
  cross-period storno" — current behaviour is correct per Skatteverket
  period-aligned filing: the reversed original stays in its own period,
  the matching storno (status 'posted') lands in the reversal period,
  and they net to zero across the full year. Adding code comment to
  document.

Tests: 2,623 passing (+6 for computeVatReport unit tests). Production
build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): address PR #393 round-3 compliance review

Compliance bot re-ran after the round-2 push and flagged three items:

VAT computation
- One-sided reverse-charge warning previously only checked 2645 (EU
  acquisitions). For domestic reverse charge per ML 16:13 (byggtjänster,
  electronics > 100k SEK, etc.) the matching input lands on 2647 — a
  correctly-balanced 2614+2647 booking would have falsely fired the
  warning. Fixed: warning now triggers only when *both* 2645 and 2647
  are zero. Updated message text mentions both accounts. Added a test
  case asserting the no-warning path for 2647-only-input.

Skill documentation drift
- quarterly-vat-review skill body still showed ruta05 source as
  "3001 + 3002 + 3003" while the runtime computeVatReport sums 32 BAS
  taxable-revenue accounts. Updated the skill table to read
  "3001–3008, 3041–3048, 3051–3058, 3071–3078" so the auditor-facing
  docs match the implementation.

outputSchema upgrade
- gnubok_get_vat_report and gnubok_vat_review_widget previously declared
  outputSchema as the bare { type: 'object' }. Replaced with a shared
  VAT_REPORT_OUTPUT_SCHEMA constant declaring period, period_label, all
  11 rutor (with descriptions referencing source accounts), summary,
  and warnings. Modern MCP clients that validate structuredContent
  against outputSchema now have an accurate contract. Added a test
  asserting the schema is non-trivial and declares every ruta the
  runtime returns.

Tests: 2,625 passing (+2 for the 2647 warning path and the
outputSchema shape assertion). Production build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): address PR #393 round-4 compliance review

The compliance bot re-ran after the round-3 push with a fresh batch.
Real findings fixed; false positives documented.

VAT computation
- Removed 3004 (Försäljning inom Sverige, momsfri / VAT-exempt) from
  RUTA_05_ACCOUNTS — round-2's expansion accidentally included it. Ruta 05
  is the *taxable* base; exempt sales must NOT contribute. New test pins
  the exclusion.
- Added 3106 (taxable EU goods supply, momspliktig) to RUTA_05_ACCOUNTS.
  Used when EU buyer's VAT number is invalid or buyer is private.
- Added ruta35 — EU intra-community goods supplies, momsfri (account
  3108). Previously omitted entirely from the rutor schema; SKV 4700
  has it as a distinct box separate from ruta 39 (services) and ruta 40
  (export outside EU). VatReportResult, VAT_REPORT_OUTPUT_SCHEMA, the
  widget table, the copy-summary block, and the quarterly-vat-review
  skill table all updated. New test covers 3108 → ruta35 mapping.
- Strengthened the comment on the posted+reversed status filter to
  document why current behavior is correct per ML 2023:200 and
  faktureringsmetoden (the bot's cross-period storno concern is a false
  positive — see commit message rationale below).

Skill content
- invoicing-rules: added explicit BFL 5 kap. 6–7 § / ML 17 kap. 22–23 §
  note that the kreditfaktura itself consumes a sequential number from
  the same (or dedicated KR-) fakturaserie. The KR- prefix is a display
  convention; the underlying löpnummer must be unbroken just like the
  regular series.
- payroll-monthly: added the missing "born 1937 or earlier → 0 %"
  cohort to the rate breakdown. Previously could lead a payroll run to
  over-pay avgifter on the oldest cohort.

False positives in the round-4 review (verified, not changed)
- 2644/2648 as reverse-charge inputs: verified against gnubok's actual
  BAS chart (lib/bookkeeping/bas-data/class-2-equity-liabilities.ts).
  2644 does not exist; 2648 is "Vilande ingående moms" (dormant input
  VAT for cash method), not RC at 6 %. Canonical RC inputs are 2645 (EU)
  and 2647 (domestic) — both already covered.
- Cross-period storno over-count: per ML 2023:200 + Skatteverket
  faktureringsmetoden, the original sale's VAT belongs to the invoice-
  date period; the kreditfaktura's reduction belongs to the storno-date
  period. Including 'reversed' status entries (which still have their
  original date) is therefore correct. *Excluding* them would
  under-report the original period and over-credit the reversal period.
  Added a multi-line comment in computeVatReport documenting this.

Tests: 2,627 passing (+2 for ruta35 mapping and 3004 exclusion).
Production build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 20:04:10 +02:00
Mattsson c03582b5c7 Fix/percistent mcp connection (#392)
* feat(oauth): add support for refresh tokens in OAuth flow and update database schema

* feat(prompts): add MCP prompts and corresponding functionality for prompt retrieval

* feat(auth): enhance error handling for refresh token operations and validation
2026-05-05 13:48:53 +02:00
Mattsson bb855d2ddc Add/ai native supp (#385)
* feat(branding): implement dynamic branding in service worker and reports

* feat(auth): enhance API key scopes and add bookkeeping write scope

- Updated transaction write scope description to include additional tools.
- Enhanced reports read scope description to reflect new functionality.
- Introduced bookkeeping write scope with relevant description.
- Updated SCOPE_GROUPS to include bookkeeping domain.
- Modified TOOL_SCOPE_MAP to include new bookkeeping operations.
- Updated validateApiKey function to return api_key_id and api_key_name for better actor attribution.

feat(tests): add unit tests for MCP resource registry

- Created tests for data resources to ensure all required fields are present.
- Added tests for resource query parsing and retrieval.

feat(resources): implement MCP resources for company and accounting data

- Added capabilities resource to expose API key capabilities based on granted scopes.
- Implemented chart of accounts resource to retrieve active BAS chart.
- Created company current resource to fetch active company details.
- Developed active fiscal period resource to check posting eligibility.
- Implemented recent activity resource to fetch latest journal entries, invoices, and transactions.
- Added VAT treatments resource to provide available VAT rates per customer type.

feat(pending-operations): introduce risk tiers for operations

- Added risk level classification for pending operations to determine auto-commit eligibility.
- Implemented functions to classify operation risk levels and identify high-risk operations.

feat(migrations): add actor model and risk tier to pending operations

- Updated pending_operations table to include actor type and risk level columns.
- Enhanced audit_log to mirror actor information for compliance.
- Modified validate_and_increment_api_key function to return actor details.
- Expanded operation types in pending_operations to include new high-risk operations.

* feat: add auto-commit functionality for low-risk pending operations

- Implemented shouldAutoCommit function to determine eligibility for auto-commit based on operation type, actor type, and company settings.
- Created commitPendingOperation function to handle execution of pending operations with consistent status updates.
- Added tests for shouldAutoCommit to cover various scenarios including high-risk operations, user actors, company opt-in status, and monetary thresholds.
- Introduced new columns in company_settings for agent_auto_commit_enabled and agent_auto_commit_max_amount to allow companies to opt-in for auto-commit functionality.
- Added SQL migration to update the database schema for new auto-commit settings.

* feat(idempotency): implement idempotency key handling for safe retries and cleanup

* feat: expand API key scopes and pending operations for bookkeeping

- Added 'suppliers:write' scope to API key scopes for supplier invoice management.
- Updated SCOPE_GROUPS to include the new 'suppliers:write' scope.
- Introduced new pending operation types for bookkeeping: close_period, lock_period, run_year_end, set_opening_balances, run_currency_revaluation, explain_voucher_gap, uncategorize_transaction, approve_supplier_invoice, credit_supplier_invoice, and convert_invoice.
- Implemented corresponding commit functions for the new operations in the pending operations module.
- Enhanced PendingOperation type to include actor model and risk level attributes.
- Added tests for new functionality, ensuring proper behavior and constraints in the database.

* feat: implement unlockPeriod functionality and related tests

* feat: add agent auto-commit settings and related functionality

* feat: add attention resource with comprehensive summary of outstanding tasks

* feat: enhance pending operations with 'committing' status and immutability checks, improve idempotency handling, and add original voucher reference for credit notes
2026-05-04 11:12:29 +02:00
Mattsson 04dbb31d7e Salary module (#245)
* feat: implement salary module with personnummer encryption, salary entries, tax tables, and AGI tracking

- Added personnummer encryption and decryption functions for secure storage.
- Created salary entries handling for journal entries including gross salary, tax withholding, and employer contributions.
- Implemented tax table lookup functionality for calculating tax amounts based on monthly income.
- Developed SQL migration for salary module including tables for payroll configuration, tax rates, employees, salary runs, salary run employees, salary line items, and AGI declarations.
- Established row-level security policies for all new tables to ensure company-scoped access.

* feat: add salary calculation modules for 2026

- Implemented engångsskatt calculation for one-time payments with tax brackets.
- Added löneväxling functionality for salary sacrifice to pension, including employer savings and warnings.
- Created pain.001 generator for salary batch payments in compliance with Swedish banking standards.
- Developed PDF template for payslips, including detailed breakdowns and employer costs.
- Generated seed data for Swedish tax tables for 2026, including SQL insert statements.
- Implemented traktamente calculations for per diem and mileage allowances, adhering to Skatteverket regulations.
- Added seed script for populating tax tables in the database.

* feat: Update meal reduction percentages in traktamente calculation

fix: Remove obsolete seed script for 2026 tax tables

feat: Extend SalaryRunStatus type to include 'corrected' status

feat: Implement KU10 XML generation endpoint for annual employee income statements

feat: Add endpoint for creating corrections to booked salary runs

feat: Implement endpoint for sending payslip PDFs to employees

feat: Create KU10 XML generator for annual reporting

feat: Add salary transaction matcher for auto-linking bank transactions to salary entries

chore: Add database migration for salary correction support

* feat: replace select elements with custom Select component for employment and salary types

* feat: enhance salary calculations with pension entry and avgifter category support
2026-04-15 11:17:39 +02:00
Jakob WennbergandClaude Opus 4.6 b387a77bfd chore: remove Sentry, consolidate migrations, add test coverage (#244)
* chore: remove Sentry, consolidate migrations, add test coverage

Remove @sentry/nextjs and all Sentry integration code — error tracking
now handled by Recapt. Consolidate 22 incremental migrations into a
single schema sync migration. Add 6 new test suites (auth, invoice
matching, VAT rules, opening balances) and extend report tests with
edge cases. Update Docker image name to gnubok, sync crontabs and
extension presets, fix CSP missing space, simplify journal entry
missing-document dialog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove viewer bank import migration never applied to production

20260413150000_viewer_bank_import_permissions.sql (PR #234) was merged
to main but never applied to the production database. It references
current_active_company_id() which does not exist in production either.
This breaks fresh installs and Supabase preview branches because the
migration runs before the consolidated schema sync.

Remove it so the migration chain matches production. The viewer bank
import RLS policies should be re-added in a future migration alongside
the helper functions they depend on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct delete policies for tables without company_id column

Seven tables in the generic delete-policy loop don't have a direct
company_id column, causing fresh installs to fail with "column
company_id does not exist". Fix by moving them out of the loop:

- invoice_items, journal_entry_lines, receipt_line_items,
  supplier_invoice_items → join through parent table
- extension_toggles, notification_settings, push_subscriptions →
  user-scoped (auth.uid() = user_id)

All policies match their existing production definitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:52:00 +02:00
Mattsson cd376e1cad feat: implement viewer role permissions for bank transaction imports and connections (#234) 2026-04-13 19:43:28 +02:00
Mattsson a1a816b4a5 Delete features (#218)
* Implement company and account deletion features

- Add event types for company and account deletion to CoreEvent.
- Enhance Supabase middleware to handle company context resolution and cookie management for archived companies.
- Create API routes for deleting accounts and companies, including necessary validations and event emissions.
- Implement tests for account and company deletion endpoints to ensure proper functionality and error handling.
- Add retention notice component to inform users about bookkeeping data retention during destructive actions.
- Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws.

* feat: enhance account deletion process and update user notifications

* Add service client for onboarding completion check and update escape hatch visibility

* Enhance invite flow and email handling for company members

* Refactor company context and RLS policies for active company isolation

- Update `switchCompany` to remove unnecessary revalidation as client handles navigation.
- Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships.
- Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility.
- Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership.
- Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization.
- Implement `CompanyTabSync` component for real-time active company enforcement across tabs.
- Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`.

* feat: implement viewer role enforcement for write permissions

- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.
2026-04-11 17:06:32 +02:00
Jakob WennbergandClaude Opus 4.6 6d75b9a1bf feat: BankID authentication via TIC Identity API (#192)
* feat: add BankID authentication via TIC Identity API

Integrate BankID as a login/signup method using the TIC Identity API.
Users can authenticate with BankID QR codes (desktop) or deep links (mobile),
link BankID to existing accounts, and skip TOTP MFA when BankID is linked.
Removes Step 0 (role choice) from onboarding for all users. Adds enrichment
data support for pre-filling company details from Bolagsverket during signup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review — server-side rate limit, unlink clears MFA bypass

- Add per-IP rate limit (5s cooldown) on /bankid/start to prevent
  unbounded billable TIC sessions from unauthenticated callers
- Add /bankid/unlink endpoint that deletes bankid_identities AND clears
  app_metadata.bankid_linked so MFA enforcement resumes after unlink
- Update BankIdSettings to call server-side unlink instead of client-side delete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move rate limiter to module scope, add BankID logo and year-end skill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:44:14 +02:00
Jakob WennbergandClaude Opus 4.6 c4a6d16e94 feat: document inbox + fix MCP pending operations user_id (#172)
* fix: always use business PSU type for bank connections

EF (sole trader) users connecting to Nordea got personal accounts
because psu_type was set to 'personal' based on entity_type. Since
gnubok is accounting software, all bank connections should use
'business' PSU type regardless of entity type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: document inbox extension with AI classification

Add invoice-inbox extension for email-based document processing with
AI-powered classification, supplier matching, and inbox management.
Includes MCP tools for document upload/listing, migration, and
supporting changes across document service, API keys, and banking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: disable invoice-inbox extension, use dynamic import in MCP server

Keep invoice-inbox out of extensions.config.json until ready for
production. MCP server now dynamically imports classifyDocument to
avoid breaking when the extension is disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct file size error message in invoice-inbox upload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:45:17 +02:00
MattssonandClaude Opus 4.6 0dd1f5ebc1 feat: multi-tenant company refactor (GNU-19) (#153)
* feat: multi-tenant company refactor (GNU-19)

Introduce companies table, company_members, and user_preferences to
support multiple companies per user. All data scoping changes from
user_id to company_id across the entire codebase.

Key changes:
- Database migration: new tables, company_id on 40+ tables, backfill,
  RLS rewrite from user_id to company-member-based, updated RPCs
- Types: Company, CompanyMember, CompanyRole, UserPreferences types;
  company_id added to all entity interfaces; companyId on all events
- Engine: all 7 core functions take companyId; storno, period, year-end
  services updated; 16 report generators updated
- Middleware: company context resolution (cookie → prefs → first company)
- API routes: ~120 routes updated with requireCompanyId()
- Frontend: CompanyProvider context, layout/dashboard/onboarding updated
- Extensions: context factory, 9 extensions, all lib files updated
- Tests: 1880 tests passing, all helpers updated with company_id defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add database migrations for multi-tenant company and team system (GNU-19)

Adds company_invitations, company creation RPC, team_members, account
deletion RPC, and teams table refactor migrations. Updates base
multi-tenant migration with cascading FKs and onboarding_step column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add team types and update core infrastructure for multi-tenancy (GNU-19)

Adds TeamRole, MemberSource, and Team types. Refactors Supabase service
client to be stateless, updates middleware for team-aware routing, extends
CompanyContext with team/role fields, and updates extension service types
to accept companyId.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: thread company_id through business logic functions (GNU-19)

Replaces user_id scoping with company_id across all lib modules:
bookkeeping, documents, transactions, invoices, reconciliation, tax,
deadlines, and import. Updates corresponding tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: thread company_id through API routes and extensions (GNU-19)

Updates all existing API routes to extract and pass companyId. Updates
enable-banking and arcim-migration extensions for company-scoped
transaction ingestion and sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add company and team management API routes (GNU-19)

Adds CRUD endpoints for company members, company invitations, team
members, and team invitations. Includes invite token utilities, email
templates, and company switch server action.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add team/company UI components, pages, and dashboard updates (GNU-19)

Adds CompanySwitcher, ConsultantEmptyState, Step0RoleChoice, company
members and team management panels. Updates dashboard layout for
team-aware routing, onboarding for multi-step role choice, and auth
callback for team invite acceptance. Ignores supabase/.branches/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add null guards for company in import page (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move appUrl declaration to outer scope in invite route (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add optional chaining for company.name in members section (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add optional chaining for second company.name in members section (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add null guards for company in extension components (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: pass companyId to executeSIEImport in arcim-migration extension (GNU-19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update tests to use companyId instead of userId and improve type handling

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:41:52 +02:00
MattssonandClaude Opus 4.6 550cadcb06 fix: harden auth, cron secrets, and provider flows (GNU-17) (#148)
- Replace === with crypto.timingSafeEqual in all 7 cron routes via shared lib/auth/cron.ts
- Add in-memory rate limiting (60 req/min) and expires_at support to calendar feed
- Add exponential backoff on MFA verify after 3 failed attempts
- Add 60s cooldown on password reset requests
- Validate bank callback auth code format before API call
- Redact session IDs from bank sync and callback logs
- Validate OAuth redirect_uris against allowlist (claude.ai, claude.com, localhost)
- Remove excessive PII/debug console logging from login page

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:49:09 +02:00
MattssonandClaude Opus 4.6 bb473e2c57 Worktree api key scopes (#139)
* feat: add read/write scopes to API keys

API keys now require explicit scopes (e.g. transactions:read,
invoices:write) instead of having implicit full access. The create
dialog shows grouped checkboxes per domain with read/write split.
Legacy keys with null scopes default to read-only. MCP tools/list
is filtered by scope and tools/call rejects unauthorized calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Enhance API key scopes with suppliers and update descriptions for better clarity

* fix: drop function before recreating with changed return type

PostgreSQL cannot change return type via CREATE OR REPLACE.
Drop the existing function first to avoid SQLSTATE 42P13.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add new migration to drop and recreate function with scopes return type

The original migration was already applied, so a new migration is needed
to DROP the function first before recreating with the updated return type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add DROP FUNCTION to original migration, remove redundant fix migration

Preview branches replay all migrations from scratch. The original migration
must DROP the function before recreating it with a changed return type,
otherwise PostgreSQL rejects the CREATE OR REPLACE. The separate fix
migration is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename migration to avoid duplicate version in schema_migrations

Version 20260325120000 is already recorded in the preview DB from a
prior failed apply. Renaming to 20260326130000 so Supabase treats it
as a new migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:30:26 +01:00
Jakob WennbergandClaude Opus 4.6 d61670231f fix: plain 401 for MCP OAuth discovery (#74)
* fix: return plain 401 for MCP OAuth discovery and harden auth flow

- Return plain HTTP 401 with WWW-Authenticate header (no JSON-RPC body)
  so Claude Desktop's MCP client can trigger OAuth discovery correctly
- Remove unused apiKey import from authorize route
- Remove stale codeChallengeMethod parameter from oauth-codes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Retry-After header to 429 rate limit response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:47:47 +01:00
Jakob WennbergandClaude Opus 4.6 5d66dd6bfc feat: MCP server, API keys, OAuth, and KPI dashboard (#72)
* fix: prevent Chrome auto-translate from crashing React during onboarding

Chrome auto-translate modifies DOM text nodes when it detects a Swedish
page (lang="sv") in a browser set to English. React does not expect
external DOM mutations and throws, crashing the entire component tree
into global-error.tsx on every step transition.

Add translate="no" and <meta name="google" content="notranslate"> to
suppress browser translation. Also fix timezone-unsafe date parsing in
fiscal period validation (new Date("YYYY-MM-DD") + getDate() returns
local-timezone values, shifting dates by -1 day in Western timezones).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add notranslate meta tag to global-error.tsx for consistency

Per review feedback — global-error.tsx renders its own <html> document,
so it needs the same <meta name="google" content="notranslate"> tag as
layout.tsx to fully suppress Chrome translation on error pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add MCP server extension with OAuth, API keys, and KPI dashboard

Let users do bookkeeping through Claude Desktop, Claude Code, or any
MCP-compatible client. "Show my uncategorized transactions." "Book that
as office supplies." "Invoice Acme for 15,000 kr."

MCP server (extension):
- 10 tools: transactions, categorization, customers, invoices,
  trial balance, VAT report, KPI report, income statement
- JSON-RPC 2.0 protocol (no SDK dependency, works in serverless)
- Tool annotations, pagination, input validation per MCP best practices
- Same engine as web UI (VAT rules, exchange rates, event emission)

API key infrastructure (core):
- api_keys table with RLS, rate limiting (100 RPM), scopes column
- Atomic rate limit via DB RPC (validate_and_increment_api_key)
- Key management API routes + settings UI panel

OAuth 2.1 for Claude Desktop connectors:
- .well-known/oauth-protected-resource + oauth-authorization-server
- Authorization endpoint with consent page
- Token endpoint with PKCE verification
- Stateless encrypted auth codes (AES-256-GCM, no DB storage)
- Dynamic client registration

KPI dashboard:
- /nyckeltal page with hero cards, operational grid, trend chart
- GET /api/reports/kpi endpoint
- Gross margin, cash position, expense ratio, avg payment days,
  VAT liability, revenue/expense trend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address OAuth security vulnerabilities from code review

Critical fixes:
- Auth code replay: Track used codes in oauth_used_codes table with
  unique constraint. Codes are single-use per OAuth 2.1 §4.1.2.
- Open redirect: Validate redirect_uri against hardcoded allowlist
  of known Claude callback URLs + localhost for dev.

P1 fixes:
- Move API key creation from /authorize to /token endpoint. Keys are
  only created after PKCE verification, preventing orphaned keys on
  abandoned OAuth flows.
- Add ensureInitialized() to MCP server so event handlers load and
  transaction.categorized events reach extensions.

P2 fixes:
- Remove 'plain' from PKCE methods — only S256 is advertised and
  accepted.
- Fix extension count in sectors test (10 → 11 for mcp-server).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove duplicate ensureInitialized() that caused circular import

The extension router (ext/[...path]/route.ts) already calls
ensureInitialized() before dispatching to handlers. The duplicate
call in server.ts created a circular import that Turbopack couldn't
resolve, breaking the Vercel build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:57:14 +01:00
Jakob WennbergandClaude Opus 4.6 928a145f9a feat: upgrade auth to email+password with optional TOTP MFA
- Replace magic-link-only login with email+password (primary) and magic link (toggle)
- Add registration page with strong password validation
- Add MFA enrollment (/mfa/enroll) with QR code and manual secret
- Add MFA verification (/mfa/verify) with 6-digit TOTP input
- Add password reset flow (/reset-password)
- Add middleware MFA enforcement gated by NEXT_PUBLIC_REQUIRE_MFA env var
- Self-hosted deployments (NEXT_PUBLIC_SELF_HOSTED=true) skip MFA entirely
- Add Security tab in Settings for password change and MFA management
- Add requireAuth() API route helper with MFA check
- Update CLAUDE.md with Authentication section and env var docs
- Update Dockerfile and docker-entrypoint.sh for new env var placeholders

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:17:49 +01:00