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.
What happens
LedgerModel::setCategoryImpl(behindSetCategory, and the rule cascade inexecute(StoreTransaction)) andBudgetModel::execute(LinkAccountToCategory)both join an account to a category by id, with no check that the two are in the
same book:
AccountRecordandCategoryRecordeach carry their ownledger, so a booktwo 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.scenariodispatches exactly this and says so:
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, sothere 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
LinkAccountToCategoryhalf is exercised on areal
ladder_ledger_serverby 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(base2a035fe8) viapython3 scripts/scenario/run_scenarios.py --rung ledger --build-dir build/ladder-srv --twice,both passes green.
Not verified: the
SetCategoryhalf (same code path, reached throughLedgerModelrather thanBudgetModel) was read, not driven. No consequencebeyond the budget report was investigated — in particular whether any GUI
surface or later report kind reads
AccountRecord::categorywithout a ledgerfilter is unknown, and that is what would size this.
What would change the verdict
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.
AccountRecord::categoryis foundthat does not independently filter by ledger.