Fava-client hardening cluster (CODE-REVIEW-2026-06 #7, #14, #15, #19
+ libra-#23, libra-#53):
- New FavaClient.transform_source_line does the whole
read-checksum-modify-write under the global write lock and maps
Fava 409/412 to ChecksumConflictError. The approve and reject
endpoints used to do this dance with raw httpx and no lock — two
concurrent mutations raced each other and every other ledger
writer (libra-#23). They now route through the new method and
translate conflicts to HTTP 409.
- update_entry_source / delete_entry raise ChecksumConflictError on
409/412 instead of leaking raw HTTPStatusError.
- One shared httpx.AsyncClient per FavaClient (12 per-call
instantiations removed — no more TCP handshake per request);
closed via libra_stop. Health probes keep their 2s timeout
per-request.
- Account names/patterns are validated against ^[A-Za-z0-9:_-]+$
before interpolation into BQL string literals.
- The posting amount regexes are consolidated into module-level
compiled patterns, all decimal-tolerant — the old integer-only
SATS pattern silently dropped decimal-SATS postings (Fava's @@->@
normalisation emits them) from balances.
- add-account no longer verifies its own write with a second
serialized get_all_accounts round-trip (libra-#53):
sync_single_account_from_beancount grows an assume_exists path.
New test: concurrent approve+reject must both land (was
lost-update/412 before the lock).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
castle_start() was using CastleSettings() defaults (slug=castle-ledger)
instead of reading the saved settings from the database. This caused all
Fava queries to 404 on instances where the ledger slug differs from the
default (e.g. demo-ledger).
Now loads settings from extension_settings table at startup, falling
back to defaults only if no saved settings exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration Components:
1. Manual API Endpoints (admin-only):
- POST /api/v1/admin/accounts/sync (full sync)
- POST /api/v1/admin/accounts/sync/{account_name} (single account)
2. Scheduled Background Sync:
- Hourly background task (wait_for_account_sync)
- Registered in castle_start() lifecycle
- Automatically syncs new accounts from Beancount to Castle DB
3. Auto-sync on User Account Creation:
- Updated get_or_create_user_account() in crud.py
- Uses sync_single_account_from_beancount() for consistency
- Ensures receivable/payable accounts are synced when users register
Flow:
- User associates wallet → creates receivable/payable in Beancount
→ syncs to Castle DB → permissions can be granted
- Admin manually syncs → all Beancount accounts added to Castle DB
- Hourly task → catches any accounts created directly in Beancount
This ensures Beancount remains the source of truth while Castle DB
maintains metadata for permissions and user associations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Initializes the Fava client with default settings when the Castle extension starts.
This ensures the client is ready to interact with Fava immediately and provides feedback if Fava is not configured correctly.
The client is re-initialized if the admin updates settings later.
Implements a mechanism to cancel pending background tasks
when the extension is stopped. This ensures proper cleanup and
prevents potential issues with lingering tasks.
Implements a background task that listens for paid invoices
and automatically records them in the accounting system. This
ensures payments are captured even if the user closes their
browser before the client-side polling detects the payment.
Introduces a new `get_journal_entry_by_reference` function to
improve idempotency when recording payments.
Removes the `castle.` prefix from database table names in queries, streamlining data access.
Updates authentication to use `WalletTypeInfo` dependency injection for retrieving wallet information. This improves security and aligns with LNBits' authentication patterns. Also modifies the main router's tag to uppercase.