# We changed a core rule two weeks in. No service had to be rewritten.

> Source: <https://dev.to/dynokore_team/we-changed-a-core-rule-two-weeks-in-no-service-had-to-be-rewritten-2b6n>
> Published: 2026-10-27 11:36:53+00:00

On 12 October 2026, two weeks into building DynoInvoice, the owner sent one line: "The system must access the database in one unified way." 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. One of those libraries had merged two days earlier.

Within about 46 minutes, 16 reviewed changes were published to the project's record: the architecture decision and the requirement were amended, 7 tasks were created, 15 existing tasks were amended, and later service work was made to depend on the change. No service had to be rewritten, because no service had been built on the old pattern yet.

If that rule had lived in a CLAUDE.md or AGENTS.md file, the edit would take a minute. Here is what the edit wouldn't do:

An issue tracker helps with the first one, if someone goes through the tickets by hand. The other three need something else.

**The decision went on the record first.** The coordinating agent had put a design question on the record with five options. The owner chose "jOOQ only, everywhere", then came back half an hour later and asked whether JPA could handle simple reads and writes. The answer set out what that would cost in tenant safety, caching and mixed transactions, and the owner settled on jOOQ only, with a thin convenience layer on top. Both rulings are records, with who decided.

**Tasks changed in the same hour.** The architecture decision and the requirement were revised through reviewed changesets, and the affected tasks were amended alongside them. One task moved itself from ready back to blocked the moment its new dependency was published, so no agent could pick it up against the old rule. Minutes later a design question found two code templates the first pass had missed, and they were added.

**The rule became a build check.** Six libraries were banned, and dependency, naming and bytecode rules now refuse the old pattern. The reviewer spent five rounds probing ways around those rules: an inherited method, a lambda, a pool subclass, reflective writers, a string bean definition. Each hole was closed or ruled on.

**Old code was converted from a list that could only shrink.** The list of modules still to convert went from four to none. A module can't be added back, and an empty entry fails the test until someone removes it. The first build-wide run of the new rule produced 48 findings, each with a reason and an owning task. None was exempted.

**The owner narrowed the rule three times.** The first build rules read the ruling too strictly and refused any reference to `java.sql`. The owner clarified that the point was one way of access and no direct SQL statements, not a ban on the types. Later: tests may use JDBC, production code may not. Each time the rule was loosened on the record, with a reason. Nobody worked around it in code.

As of 26 October 2026, apart from three named exceptions (the tenant kernel's connection core and the wiring that hands connections to jOOQ), DynoInvoice's production code has no JDBC statements.

```
# allowlist.txt: files still allowed to use the old pattern. It may only shrink.
pattern='java\.sql\.(Statement|PreparedStatement)'
found=$(grep -rlE "$pattern" src/main | sort)
new=$(comm -23 <(echo "$found") <(sort allowlist.txt))
gone=$(comm -13 <(echo "$found") <(sort allowlist.txt))
[ -z "$new" ]  || { echo "new use of the old pattern:"; echo "$new"; exit 1; }
[ -z "$gone" ] || { echo "clean now, remove from allowlist.txt:"; echo "$gone"; exit 1; }
```

It fails on any new file that uses the pattern, and on any listed file that is already clean, so the list can't quietly keep entries it no longer needs. Plant a new use and check it goes red before you trust it.

In Keel, requirements, architecture decisions, rules and tasks are small versioned records with typed links, and design questions are answered on the record with who decided. Here, the ruling, the decision and the tasks were revised through changesets that were reviewed before they were published. A task with an unmet dependency is held back as blocked, which is why one task moved itself back to blocked the moment its new dependency was published.

The build check and the shrinking list live in DynoInvoice's own build. Keel holds the decision they enforce. The full timeline, with commits, is on [When a decision changes](https://dynokore.com/work/dynoinvoice/change/).

*Written by the DynoKore team with AI assistance. First published on [dynokore.com](https://dynokore.com/blog/one-rule-22-tasks/). Events from DynoInvoice's public repository and Keel record, October 2026; counts as of 26 October 2026. Owner quotes are translated from chat. We're opening DynoKore Keel to a small group of design partners: [dynokore.ai](https://dynokore.ai/?ref=devto).*
