Executable Boundaries for AI-Assisted Java Changes: a MeshSync-Inspired Case Study
A proposed modular Quarkus design for making selected architectural drift visible before human review.
Coding agents change the cost of producing a plausible patch. They can also place a plausible patch in the wrong layer, couple contexts for convenience, or quietly widen a public contract. Those changes may compile and pass a narrow test.
This is a MeshSync-inspired target architecture, not a description of a deployed backend or evidence that a particular suite, worker, migration, or CI pipeline exists. It is a design case study for architects and senior Java engineers considering coding agents in a modular Quarkus service.
Its claim is deliberately narrow: executable boundaries, typed contracts, explicit domain policy, and layered verification can contain or detect selected kinds of architectural drift in AI-assisted changes. “Anti-hallucination” is, at most, a metaphor. These controls cannot discover fabricated requirements, a semantically wrong domain model, insecure logic, missing cases, or errors that stay inside an allowed layer. They make a change eligible for human review; humans still decide whether it is correct.
flowchart LR
A[Coding-agent contribution] --> B[Defined boundaries and contracts]
B --> C[Deterministic checks]
C --> D[Selected failures made visible]
D --> E[Change eligible for human review]
E --> F[Human product and engineering judgement]
The risk is plausible architectural drift
Compilers catch defined syntax and type errors. Tests exercise only the inputs and assertions they contain. Neither establishes that a behaviour belongs in the right context, that an endpoint preserves a client expectation, or that a worker result is safe to act on.
That matters when an agent works by analogy: it may inject persistence into a domain object, mutate through a query path, serialize an aggregate because the fields look convenient, or add Map<String, Object> to avoid understanding a payload. Each shortcut can be locally reasonable and globally expensive.
The useful response is not to pretend that DDD makes an agent trustworthy. It is to turn the few rules worth defending into constraints with clear ownership and feedback.
A proposed map: contexts are not modules
In this proposal, business contexts might include storage, catalogue, library, identity, marketplaces, and analytics. A context decision depends on its language, invariants, and lifecycle. For example, a pricing recommendation might be analysed with analytics, but that is a design decision to test against ownership and lifecycle, not a universal placement rule. The owner of thumbnail processing likewise depends on the use case.
Technical Gradle modules are a different concern. The following is an illustrative map, not an inventory of an existing repository:
| Proposed unit | Responsibility | Dependency posture |
|---|---|---|
| Business-context modules | Domain language, use cases, local adapters | Depend inward; integrate other contexts through explicit contracts |
| Shared kernel | Small, stable cross-context concepts | Deliberately narrow; growth creates central coupling |
| Generated-client module | Schema-derived worker/client types | Kept at an integration edge, not in the domain |
| Boot/composition module | Framework wiring and deployment composition | May assemble implementations; owns little business behaviour |
| Verification module | Architecture and compatibility checks | Inspects intended modules without becoming a production dependency |
Gradle dependencies can make an unwanted module inaccessible at compile time. They do not, by themselves, prove bounded-context ownership. A shared kernel is particularly tempting to an agent because it is easy to import; it needs a named owner and a high bar for additions, or it becomes a dumping ground for abstractions that should remain local.
Within a context, a conventional hexagonal direction keeps transport and integration choices outside the business model:
flowchart LR
HTTP[HTTP adapter / presentation DTO] --> APP[Application command, query, or use case]
APP --> DOMAIN[Domain]
APP --> PORT[Outbound port]
PORT --> ADAPTER[Infrastructure adapter]
ADAPTER --> DB[(Database)]
ADAPTER --> WORKER[Worker or external service]
The presentation adapter maps requests to application inputs; it does not reach directly into persistence. The application layer coordinates a use case and calls an outbound port. Infrastructure implements that port. This does not make every mapping pleasant, but it makes the dependency direction inspectable.
CQRS only where intent needs separation
Pragmatic CQRS here means separating commands from queries when that makes a use case easier to reason about. It does not require separate read and write databases. A command makes a requested transition explicit; a query is intended to return information. They can share a persistence model and a local database transaction where appropriate.
A command/query bus and predictable handlers earn their indirection when there are cross-cutting dispatch concerns, many use cases, or a real need to make read/write intent visible. For a small feature, an explicit application serviceor a simpler feature-modular designcan be clearer. CQRS is not intrinsically an AI guardrail.
Transaction ownership must be explicit rather than inferred from naming. In this proposed Quarkus design, an outer command adapter could establish a Quarkus-managed transaction, then persist aggregate changes and an outbox record in the same local database transaction. Quarkus documents the relevant transaction boundaries and rollback behaviour for its transaction support in its transaction guide. A relay publishes later; the transactional outbox pattern commonly entails at-least-once delivery, so consumers need idempotency.
An annotation-placement rule can check where @Transactional appears. It cannot prove that an interceptor ran, that rollback occurred, or that two writes were atomic. Integration tests must establish those runtime properties.
Typed contracts are boundaries, not decoration
Stable contract surfaces deserve names and validation: HTTP request/response DTOs, commands and queries, port inputs and outputs, events, and worker messages. A UserId value object or a PageRequest can make an invariant visible, though neither is free: each introduces mapping, validation, and onboarding cost.
The target design would avoid raw Object and unconstrained object maps at those stable boundaries, while allowing explicit escape hatches for genuinely extensible metadata. PageRequest should validate bounds and specify deterministic ordering; otherwise pagination still has ambiguous behaviour. Enum-bearing external contracts also need a forward-compatibility decision: unknown values may become an explicit UNKNOWN/manual-review path rather than an accidental parse failure or silent approval.
var is different. Java local-variable inference retains static typing, as JEP 286 explains. If a team bans it, that is a diff-readability convention when the inferred domain type matters, not stronger typing. The important point is to make consequential concepts explicit at contract boundaries, not to mistake style for safety.
Verification is layered, and each layer has limits
Architecture checks are useful as a second reviewer only when their capability is stated honestly. ArchUnit can inspect Java bytecode and express rules about dependencies, package placement, annotations, and direct calls. It cannot judge product intent or runtime inference.
| Mechanism | What it can establish | What it cannot establish alone |
|---|---|---|
| Gradle/module dependencies | Compile-time accessibility between modules | Correct ownership or runtime behaviour |
| ArchUnit/bytecode rules | Selected visible dependencies, placement, annotations and direct calls | Indirect behaviour, intent or all runtime effects |
| AST/static analysis | Typed and syntax restrictions | Semantic correctness beyond the rule |
| Lexical source scans | Heuristic warnings for configured patterns | Completeness; they have bypass and false-positive risk |
| Unit, integration and contract tests | Selected runtime semantics | Cases not represented by their inputs and assertions |
For example, a scan for calls named save, delete, persist, update, or flush can flag configured direct calls in query handlers. It does not prove that every query is side-effect free. Nor does a rule requiring use of an application dispatcher prove a transaction actually wraps a database operation.
Rules need their own negative fixtures: a deliberately forbidden dependency, annotation placement, direct write call, and bypass-shaped example should fail. The CI task also needs to scan every intended module. These are proposed verification practices, not claims about MeshSync’s current build.
Two AI roles, two boundaries
There are two separate uses of “AI” in this design:
- Coding agents propose Java changes. Module boundaries, contracts, branch protection, deterministic validation, least-privilege tool access, and a human code owner constrain that contribution process.
- Runtime AI or heuristic workers return product suggestions, such as metadata enrichment, recommendations, or IP-risk signals. Their uncertain output crosses a typed integration boundary and consequential state transitions remain domain policy.
ArchUnit governs neither model inference nor a worker’s confidence. Conversely, a recommendation state machine does not constrain a coding agent. The shared principle is narrower: uncertain output should cross an explicit boundary, and actions with consequences should be expressed as policy.
Proposed worker protocol
Generated names and Java types do not alone create runtime compatibility. A proposed end-to-end protocol needs one authoritative, versioned schema and identifiers for the job, idempotency key, tenant, asset revision (or content hash), and worker-contract version. Command handling creates the outbox record in the same local transaction as the state change; a relay submits the work later.
Callbacks should be authenticated and signed, validated before use, and accepted only within a replay window. The callback handler needs duplicate and out-of-order completion handling, monotonic job transitions, schema-skew handling, observability, retry/dead-letter/reconciliation paths, and manual recovery. Webhook advice such as verifying signatures, responding appropriately, and handling deliveries defensively is well illustrated by GitHub’s webhook best practices, though a service must define its own threat model.
Job status and analysis decision are separate. “Delivered” or “completed” means a worker reached a transport outcome; it does not mean an asset is safe, a recommendation is accepted, or a business decision has been made.
Illustrative recommendation and IP policy
An illustrative recommendation state machine might be PENDING → ACTIVE → ACCEPTED | DISMISSED | EXPIRED. Confidence, normalisation, display thresholds, scores and events are product choices, not guarantees: a model-provided confidence signal is useful only after its calibration has been validated for the relevant use.
stateDiagram-v2
[*] --> PENDING
PENDING --> ACTIVE: validated result
PENDING --> EXPIRED: expiry
ACTIVE --> ACCEPTED: authorised user action
ACTIVE --> DISMISSED: authorised user action
ACTIVE --> EXPIRED: expiry
An IP-analysis worker is even more clearly evidence rather than authority. Automated analysis is not legal clearance; trademark likelihood-of-confusion analysis is contextual, as the USPTO explains, and copyright protection questions have their own limits and facts, reflected in the US Copyright Office FAQ.
In one proposed publishing policy, NOT_REQUIRED is distinct from REQUIRED_NOT_STARTED. A decision and any acknowledgement are bound to the asset revision/content hash, tenant, policy version, and worker-contract version. A stale, missing, errored, timed-out, contradictory, or unknown result blocks publication or requires an explicit authorised, audited override; it never silently becomes approval. An invalid or conflicting worker field becomes an invalid/manual-review outcome. Publish, callback, acknowledgement, and asset edits need race controlsuch as optimistic versioningso that a decision cannot be applied to a changed asset.
This is policy design, not a claim that one table of scores makes risk objective. It makes who may decide, on which revision, and with what evidence inspectable. Broader AI risk governance still needs organisational controls; the NIST AI RMF and its Playbook are useful framing resources.
Traceability and migration are supporting evidence
Requirement or technical-remark annotations can be proposed as navigation and reporting support. A report proves declared linkage, not requirement coverage or correctness. If a team adopts them, it should validate canonical IDs, find stale references, and require human review of the linkage rather than treating an annotation as a receipt from an agent.
Likewise, OpenAPI diffing tests API-description and schema compatibility, not migration completeness. The OpenAPI Operation Object defines operationId; preserve it where client tooling relies on it. Tools such as OpenAPI Diff can compare a version-controlled baseline with a proposed API, but a Quarkus replacement for a NestJS service also needs black-box or consumer tests for authentication, tenant isolation, defaults, errors, and observable behaviour.
Data migration, expand/contract deployment, rolling-version compatibility, cutover, observability, rollback, and operational ownership remain separate work. A structurally compatible description does not prove any of them.
A compact verification plan
For a design like this, the valuable checks are specific rather than grand:
| Concern | Evidence to seek before review |
|---|---|
| Aggregate policy | Unit tests at valid and invalid transition boundaries |
| Command and outbox | Integration tests for rollback of aggregate change and outbox record together |
| Callback safety | Signed, duplicate, stale, out-of-order and malformed callback tests |
| Concurrency | Optimistic-lock races across publish, acknowledgement, callback and asset edit |
| Worker operation | Retry, dead-letter, reconciliation and manual-recovery tests or runbooks |
| Migration | OpenAPI baseline diff plus black-box/consumer behaviour tests |
| Architecture rules | Negative fixtures and a task that covers all intended modules |
None of this automatically proves security, correctness, reliability, or speed. The design chiefly targets modifiability, dependency control, reviewability, contract stability, and change isolation.
Choose the weight of the guardrail deliberately
This approach costs mappings, buses, duplicated models, build time, false positives, rule maintenance, onboarding, and pressure towards central shared abstractions. It is often not worth it for a small CRUD system, boundaries that are still poorly understood, or a team unable to maintain the verification rules.
A feature-modular monolith with package checks, explicit application services, a small set of contract tests, and human-owned review may be the better first step. More formal contexts, ports, and architecture rules become worthwhile where independent change, integration risk, regulatory consequence, or multiple contributors make accidental coupling expensive.
The conclusion is not that an architecture can make a coding agent correct. It can narrow some invalid contribution paths and make selected drift visible. That is enough to improve the review conversation~provided humans retain ownership of the requirements, the policy, and the decision to merge.