DynoInvoice · Maintainability
A system stays maintainable, or doesn’t, at the moment someone changes their mind. On 12 October 2026, two weeks into DynoInvoice, the owner ruled that the whole system must reach its database one way only. Library code merged days earlier did it another way. This page follows that change from the ruling to the last merge, and shows why the old pattern can’t quietly come back.
“The system must access the database in one unified way. Some places using JDBC and others Spring JPA is not allowed.”
The owner, 12 October 2026 (translated from chat)
At that moment, 29 production files in six shared library modules talked to the database through plain JDBC, which the standards had allowed until then. The message queue library, for one, had been merged two days earlier (9adb93cc).
Soon after, the coordinating agent had a design question on the record with five options. The owner chose “jOOQ only, everywhere”. Half an hour after that the owner came back with a question of their own: could JPA handle simple reads and writes while jOOQ handled complex queries? The answer laid out what that would cost in tenant safety, caching and mixed transactions, and the owner settled on jOOQ only, with a thin object-oriented convenience layer on top. Over the following week the owner ruled nine times on this change; everything else was carried out by agents.
Within 46 minutes of the ruling, 16 reviewed changes were published: the architecture decision and the requirement were amended, seven new tasks were created and fifteen existing ones amended, and every later service task was made to depend on the conversion. One task moved itself from ready back to blocked the moment its new dependency was published. Minutes later a design question found two code templates the first pass had missed, and they were added.
Six standards rows were rewritten and six libraries banned. The build now refuses the old pattern through dependency bans, naming rules and bytecode rules. The reviewer spent five rounds probing ways around those rules (an inherited method, a lambda, a pool subclass, reflective writers, a string bean definition) and each hole was closed or ruled on.
The conversion ran as separate tasks per module, each reviewed and merged. Four libraries shipped new major versions marked as breaking in their changelogs. A list of modules still to convert could only shrink, from four to none; a module can’t be added back, and an empty entry fails the test until it is removed.
The first build-wide run of the new rule produced 48 findings: 20 defects in the guard itself, 23 real findings and 5 in test code. Every one got a cause and an owning task. None went to an exemption list, a suppression or a baseline. The final adoption task ran with the guard on and zero findings (a25cae87).
A conversion should change how code talks to the database, not what it does. Review caught one that would have refused a replay marker the code used to accept; the old behaviour was kept and pinned by a test that fails without the fix. Another review caught a retention template that measured a five-year legal minimum in 360-day years, reproduced it on a real database and fixed it.
The first build rule read the owner more strictly than intended. The owner clarified, and the rule was loosened on the record instead of being worked around.
“The original requirement is to unify the way the database is accessed and avoid using direct SQL statements, not that java.sql may not be referenced.”
The owner, around 16 October 2026 (translated)
“This principle does not target test code, but test code must follow best practice.”
The owner, 18 October 2026 (translated)
| When (SGT) | What happened | Public commits |
|---|---|---|
| 10 Oct | Message queue library merged using plain JDBC, as the standards then allowed | 9adb93cc |
| 12 Oct 17:24 | Design question raised with five options; the owner chose jOOQ only five minutes later | — |
| 12 Oct 17:30 | Architecture decision and requirement amended; 4 tasks created, 10 amended; later service tasks made to depend on the change | — |
| 12 Oct 17:37–18:08 | Missed templates added; standards rewritten; the owner’s follow-up question answered; 3 more tasks created, 5 amended | — |
| 13 Oct | Pending-list ratchet steps from four modules to none | 0291dcdb, c93f72ba, 2cbf3823, a2901fed |
| 14 Oct | Build enforcement lands after five review rounds of bypass probes | build rules repository (not mirrored) |
| ~16 Oct | First clarification from the owner; build rule loosened on the record; 21 tasks re-worded | — |
| 16–18 Oct | Runtime, tenancy kernel, audit log, evidence store, config resolver and message queue converted; four breaking releases | 5760b015, ba18702d, f917a0cb, 2c7f6484, 6c256fc7, d6c45165, 17dadab1, 6e7c437a, 2628cef7 |
| 18 Oct 13:13 | First build-wide run: 48 findings, each assigned, none exempted | — |
| 18 Oct 17:24 | Adoption with the guard on: zero findings | a25cae87 |
| 19 Oct | Convenience layer merged; the last template updated | daaaaabd |
Code written by agents drifts when a decision lives only in a chat or a ticket, because the next session never sees it. Here the decision went into the record first, as an amended requirement and architecture decision with tasks attached, before any code changed. Every agent that touched this work afterwards read the rule from the same place.
Then the rule became something the build enforces, not something people have to remember. The old pattern can’t come back without the build refusing it, and the list of exceptions can only get shorter.
First readings miss things: two templates on day one, an over-strict build rule, sites added by merges after the inventory was taken. Each was found by a question, a review or a gate run, and fixed on the record. That is the honest version of maintainable: not that nothing is missed, but that misses surface and get fixed where everyone can see them.
The guard’s own test is public: SqlGuardsTest.kt. Production code now has no JDBC statements outside three named exceptions (the tenancy kernel’s connection core and the wiring that hands connections to jOOQ); test code may still use JDBC.
More: when green isn’t enough · why does this line exist? · reviewing like an attacker · when the spec runs out · one requirement and everything recorded about it · eight traces from requirement to merged code