Sandcastle and mould: building against a declared architecture.
An Include helper in DATAMIMIC EE needed DescriptorLocation. It imported the class directly from an internal Errors module, although the same class was already available through the public API. That tiny choice is the kind of thing I want to catch while a refactoring is still taking shape.
from datamimic_ee.errors.context import DescriptorLocation
from datamimic_ee.errors import DescriptorLocation
In that package review, 340 architecture tests and 270 contract tests passed. The focused candidate suite passed all 123 tests too. The import was one of three interface violations ArchKeel reported. The package wasn't accepted until they were resolved.
It was the same kind of boundary problem I described in the first article. That CE refactoring had also taught us to question our verification: some comparisons had loaded code from the wrong revision. We invalidated those results and checked the origins of the loaded modules.
A reader asked how many interface violations agents would catch and repair if we moved ArchKeel before commit handover. I can't give a reliable total: our ledger didn't consistently separate autonomous pre-commit repairs from findings raised during review. I can show the EE cases we recorded and what changed because of them.
EE Core parses and executes test-data descriptors and coordinates data sources, exporters and runtime state. We wanted to align its structure with CE, make component responsibilities and APIs explicit, and prepare Authoring and Error Handling for a later migration into CE. We merged the refactoring on October 18.
I develop both DATAMIMIC and ArchKeel. Agents did much of the implementation and additional review. This is our experience using our own tools on a real codebase.
I like the moment when an architecture starts to make sense: you can follow the dependencies, the responsibilities have a home, and the interfaces tell you how to use the pieces. That is the shape I wanted for EE. The sandcastle mould is how I picture giving that shape to an agent before it starts building.
That meant writing down components, responsibilities, public interfaces and permitted dependencies in the contract. ArchKeel checks supported structural rules deterministically against collected source evidence. We can inspect a deviation, repair it and run the same check again.
Without a persistent contract, the decisions live partly in documentation, partly in earlier conversations and partly in whoever reviews the next change. A new agent session has to recover them. A working internal import is easy to accept when the architectural decision behind it isn't in context.
The public interface was there; the helper had taken a path through the wall beside it. Changing that type-only import left the runtime AST unchanged, and the native interface finding disappeared.
The caller now uses the declared interface. This illustration explains the import boundary; the actual correction was type-only.
The other two findings exposed missing publications of two constants at their already-planned owner. We published those exact symbols through the enclosing interfaces. We didn't open whole packages or add dependency directions.
The archived test logs and review decisions record these corrections. The wider refactoring still had other unresolved findings at that point.
Another EE package exposed two whole-module imports where the contract published named operations, and a private function exposed through a public alias. Review held the package. We corrected the imports and the defining name, preserving the function bodies. The next review confirmed that all three native findings had disappeared without broader access grants.
The immediate consequence of the Errors import was small. It didn't change runtime behavior. My concern is what happens as the next changes build on it.
An existing import is an example for the next developer or agent. If more callers copy it, the internal module acquires consumers outside its intended boundary. A later Errors refactoring then has to find and migrate those consumers too. Eventually, changing an implementation detail can become a compatibility decision. Whole-module access creates a similar problem: it exposes more operations than the component intended to offer.
That's the dependency problem I'd expect to prevent. We didn't measure its long-term cost in EE.
For longer refactorings, I want the same architectural decisions to survive many small changes and agent handovers. The contract survives a context reset. The next implementation is checked against it again. Changing the boundary becomes an explicit decision for review.
A hand-written test could catch the Errors import too. Someone would have to define the policy, implement the check and keep it correct through moves, re-exports and incomplete analysis. The EE package already had hundreds of architecture tests; their green result didn't establish the target's interface boundaries.
That is why I built ArchKeel: to reuse the machinery for checking architectural intent across projects. A project supplies its decisions. Counterexamples and checker fixes can then benefit other projects, instead of becoming another private scanner to maintain. EE exposed gaps in that machinery too.
The Errors import had a clear repair: use the published interface. The target itself needed a harder review. At one checkpoint, CE and EE each had 151 declared components. When I opened the lower levels of the report, I couldn't follow the EE structure all the way through. Nine common scopes were missing, including parts of models, parsers, statements and tasks. Some responsibility fields were generic sentences produced by helper scripts.
I rejected that state. A populated field hadn't given us a specified design. I wanted to see which operation a caller needed, which data crossed the boundary, who owned mutable state and connections, and how failures were handled. The first expanded target still published helpers automatically and contradicted connection ownership. That needed correction too.
That is where the report became useful to me as an X-ray. I could follow the structure into the details and ask the agent about a specific connection or ownership decision. The target was open to that review too.
The highlighted passage is an illustration of a connection worth inspecting, not a recorded EE finding or an ArchKeel screenshot.
The read-only Scripting API gave us another reason to look past the declarations. Review reproduced mutation through returned sets and bytearrays while all thirteen existing API tests passed. We corrected the snapshot boundary and exercised it in 81 focused cases and three real descriptor scenarios. Calling a component read-only hadn't established that behavior; ArchKeel's structural checks couldn't establish it either.
I also had to ask whether we could maintain the mould we were building. The inventory check found stale paths and missing files. We removed 1,156 exact module selectors already covered by the same owner's package selector. Even after that cleanup, the target contained 25,671 physical JSON lines across 37 contracts. Those are lines of text, not a rule count, and they remain a maintenance cost. I want each decision defined once, close to the component that owns it.
We also had to investigate a timeout in ArchKeel's own collector. Profiling and synthetic controls led to an indexed lookup fix in PR #603. The architecture job then passed on the previously failing runner. The planned second stability replay wasn't executed, so that result doesn't establish repeated-run stability. We started on published ArchKeel 2.0.0 and finished on 4.0.1. A missing module detail page in an earlier report also needed a fix in our report packaging.
The final pipeline passed all 37 jobs, including demos, service-backed tests, ML and determinism checks. It recorded 9,231 passing tests.
The final architecture report showed zero declared violations. Its overall declared-rules status was still UNKNOWN. It retained 92 UNKNOWN analyzer records, and validation returned 427 advisories.
Separately, static call resolution was 75.77%. That measures how much of the observed call structure the analyzer could resolve, not how much of the architecture was verified.
For the parser comparison, I wanted to avoid repeating the CE mistake of comparing the wrong code. We checked the origins of all 516 loaded EE modules in each process and compared 1,520 descriptors on two late revisions. Accepted and rejected inputs matched, along with messages, issue codes, typed locations and ordered statement trees. That gives us parser parity for that correction interval. It doesn't establish parity for every generated output from the original baseline to the final merge.
We accepted the refactoring with those limitations visible. The passing behavior suites didn't close the gaps in the architecture evidence. I didn't want zero violations to be mistaken for proof that everything was correct.
I can't put a productivity percentage on this. We didn't run a controlled comparison or keep a complete time ledger. Nor do these examples establish which defects a human reviewer would have found without ArchKeel.
The follow-up work is to identify which manual architecture gates can be replaced by native checks. ArchKeel #602 tracks the gaps. A replacement has to reject a forbidden change, accept a valid control and leave missing evidence unresolved. Runtime and compatibility tests retain their own job.
What I want to leave behind in EE is an architecture the next developer or agent can still understand: why a component owns a piece of work, which interface to use, and where a change belongs. I like building that kind of structure. ArchKeel gave me a way to keep checking it as the agents worked, and to send them back when the design itself wasn't ready.