Layering-rule violation found in a sweep against the new "entrypoint → service → mapper" rule (CLAUDE.md). Not a functional bug — behaviour is correct; this is maintainability/tech-debt.
File: business-rian/src/main/java/net/democracycraft/business/listeners/FirmBalanceTaxListener.java
Violations:
FirmAccountsMapper directly (field at line 42, constructor param 50) and calls firmAccountsMapper.listAccountsByFirm(firm.getFirmId()) at line 112 — an event listener reaching the persistence layer, skipping the service layer. Every other DB read in this plugin goes through a *Service.buildFirmCollections (lines 109–174) does proportional split, 2dp settlement, and largest-account drift correction. That's business logic that belongs in a service, not an event handler.Fix: expose account listing through a service (a read method on FirmAccountService/FirmService) and move the tax-collection computation into a FirmBalanceTaxService (interface + impl under services/, bound in BusinessModule). The listener should shrink to: on TaxCycleEvent (WEEKLY, enabled) → call the service → log the batch result.
Discovered alongside the CLAUDE.md layering-rules addition; filing for a later pass.