Skip to content

ledger: an account may be linked to another book's category (documented, unenforced) #373

Description

@Yaraslaut

What happens

LedgerModel::setCategoryImpl (behind SetCategory, and the rule cascade in
execute(StoreTransaction)) and BudgetModel::execute(LinkAccountToCategory)
both join an account to a category by id, with no check that the two are in the
same book:

// examples/ledger/src/models/ledger_model.cpp (setCategoryImpl)
auto accountRows  = mapper.Query<db::AccountRecord>()
                        .Where(FieldNameOf<&db::AccountRecord::id>,  "=", *action.accountId).All();
auto categoryRows = mapper.Query<db::CategoryRecord>()
                        .Where(FieldNameOf<&db::CategoryRecord::id>, "=", *action.categoryId).All();
if (accountRows.empty() || categoryRows.empty()) {
    throw NotFound{"SetCategory: no such account or category"};
}
accountRows.front().category = categoryRows.front();

AccountRecord and CategoryRecord each carry their own ledger, so a book
two account can be given a book one category and the row is written.

Why this is filed as a question, not a defect

Unlike morph#367, this is already documented as accepted, in the corpus
itself — scripts/scenario/scenarios/ledger/two-books-are-isolated.scenario
dispatches exactly this and says so:

The second book's account, linked to the first book's category. The link
itself is accepted -- categories and accounts are joined by id, not by book --
but the report below must still not count it, because the report filters
journals by the budget's own ledger.

and the scenario then asserts the budget report is unaffected (it counts book
one's 40.00 and not book two's 15.00). Neither action carries a ledgerId, so
there is no scope argument to filter on: enforcing this would mean comparing the
two rows' own ledgers, which is a different change from #367's.

So the open question is only whether a cross-book link should be refused
rather than merely ignored downstream — a stored row that no report can ever
act on is still a row that a future report, or a GUI listing categories for an
account, could act on by mistake.

Verification status

Partially reproduced. The LinkAccountToCategory half is exercised on a
real ladder_ledger_server by the scenario quoted above, which passes — i.e.
the cross-book link is accepted and the report ignores it. Measured on branch
worktree-agent-a99217a5bca2f10a4 (base 2a035fe8) via
python3 scripts/scenario/run_scenarios.py --rung ledger --build-dir build/ladder-srv --twice,
both passes green.

Not verified: the SetCategory half (same code path, reached through
LedgerModel rather than BudgetModel) was read, not driven. No consequence
beyond the budget report was investigated — in particular whether any GUI
surface or later report kind reads AccountRecord::category without a ledger
filter is unknown, and that is what would size this.

What would change the verdict

  • Close it as intended behaviour if the scenario's stated rule ("joined by
    id, not by book; reports filter by ledger") is confirmed as the design, ideally
    by moving that sentence into the rung's design notes rather than leaving it in
    a scenario comment.
  • Turn it into a bug if any consumer of AccountRecord::category is found
    that does not independently filter by ledger.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: ladderenhancementNew feature or requesttriage: rescopeReal problem, wrong framing; rewrite before building

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions