_open_directive_exists hardcoded '^YYYY-MM-DD open ' (dash-only, 2-digit,
single-space), but Beancount's DATE token (parser/lexer.l) is
(17|18|19|20)[0-9]{2}[-/][0-9]+[-/][0-9]+ and inter-token whitespace is any
[ \t\r] run. So a validly-formatted existing Open written as '2024/3/5 open X'
or '2020-01-01 open X' escaped detection → duplicate Open appended →
bean-check rejects the file. Anchor on Beancount's actual date pattern and
[ \t]+ separators. Adds parametrized coverage for slash/single-digit/multi-
space/tab variants.
Found in a coherence pass over the Beancount source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When add_account reported the Open already existed, the endpoint raised
409 before the DB-mirror step — so an account present in the ledger but
missing from libra's DB (a prior sync failure with no cross-DB atomicity,
or an out-of-band open) was stranded: invisible to permissions with no
recovery path. Now 409 only when the account is already in the DB too;
otherwise sync it and return success. Adds a recovery test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
get_or_create_user_account opened per-user receivable/payable accounts
constrained to EUR/SATS/USD, so a posting in any other currency tripped
'Invalid currency CAD/GBP/JPY for account Assets:Receivable:User-…' at
bean-check — the exact errors the optional-currencies work set out to fix,
which had only reached the admin chart-account path. Open user accounts
unconstrained (currencies=None) so they hold arbitrary fiat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The existence check matched 'open <name>' anywhere in the chart source,
so a prior account's description metadata or a comment mentioning the
name produced a false 409, while a real directive with an inline comment
and no space ('open X;legacy') was missed → a duplicate Open was appended
and bean-check then rejected the file, breaking every later /api/source
write. Extract the check into a pure _open_directive_exists() anchored to
'^YYYY-MM-DD open <name>' with an account-boundary negative-lookahead, and
unit-test both failure directions plus prefix/child non-matches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test harness was never updated to the post-server-deploy#4 split ledger
layout, so libra's per-user account opens (routed to accounts/users.beancount
by fava_client._infer_target_file) 500'd as a 'non-source file' and fell back
to DB-only — breaking the balance test and contributing to settlement errors.
Make the harness ledger a faithful split (root includes accounts/chart.beancount
+ accounts/users.beancount; title stays in root so the slug still matches).
Also raise lnbits_rate_limit_no for the session: the full suite fires >200
req/min and the default limiter 429'd fixture setup intermittently (10-11
errors). The limiter is built once at app creation, so setting it in the
session settings fixture (before the app fixture) disables it suite-wide.
Net: full suite goes from 1 failed / ~10 errors to fully green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The endpoint only checked the root prefix, so a direct API call (bypassing
the UI) could write a malformed Open directive into the ledger source.
Add _validate_account_name mirroring Beancount's core/account.py grammar
(root [\p{Lu}][\p{L}\p{Nd}-]*, sub [\p{Lu}\p{Nd}][\p{L}\p{Nd}-]*, >=1
sub-account) — verified to match beancount.core.account.is_valid across
20 cases incl. Unicode, digit-start subs, hyphens. Align the client
segment regex to the same rule (was ASCII-only, rejected valid names).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Free-typing the full hierarchical name let admins fat-finger the parent
(wrong/invalid root). Replace the single name field with a required
Account Type select (the 5 valid roots, mirroring _VALID_ACCOUNT_PREFIXES)
plus a sub-account input, a live 'Will create: ...' preview, and
per-segment validation (each part must be a capitalized Beancount
account component). The root prefix is now structurally guaranteed valid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add_account no-ops if the Open directive is already present but returned
a normal-looking dict, so the admin endpoint reported success ('created
(sync pending)') for a duplicate. Return an already_existed flag and
raise 409 from the endpoint. Also anchor the existence check on the Open
directive with a trailing-boundary match so a prefix (Expenses:Gas)
doesn't match a longer sibling (Expenses:GasStation). The flag is
additive, so the idempotent user-account path keeps no-opping silently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The UI omits currencies so the Open directive is written unconstrained,
but the model defaulted currencies to ["EUR","SATS","USD"], so Pydantic
refilled them and the endpoint passed the constraint through — every
admin-created account got a currency-constrained Open (which would
reject postings in other currencies, the same CAD/GBP/JPY bean-check
class we hit on user accounts). Default to None so omission reaches
add_account and the directive is unconstrained; an explicit list still
works for API callers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface the existing POST /api/v1/admin/accounts endpoint in the UI: a
super-user-only 'Add Account' button on the Chart of Accounts card opens
a dialog for the hierarchical account name + optional description, posts
with the wallet admin key (require_super_user), then reloads accounts.
Client-side prefix validation mirrors the server's _VALID_ACCOUNT_PREFIXES.
No currency input — an Open directive does not require currency constraints.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add_account wrote free-text metadata values straight into the ledger
source via /api/source with no escaping — an unescaped quote or newline
in an admin-supplied description would corrupt the Beancount file (or
forge extra metadata lines). Escape backslash/quote/newline per the
tokenizer's cunescape rules (verified round-trip through beancount's
parser). Also make the currency constraint list optional so an Open
directive can be written unconstrained (currencies are an optional
part of the directive, not required).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>