From Architectural Insight to Operational Discipline
In a recent essay, From Scaffolding Convenience to Architectural Truth, I described how the architecture of LaravelUi5 did not emerge from a master plan.
What we examined there initially looked like tooling friction. Only by looking closer did it reveal unfinished work in properly naming and committing to the domain model, and how that gap was eventually closed once it was explored.
This article starts where that reflection ends. Because architectural clarity, once reached, has consequences.
One of those consequences is the emergence of a second artifact alongside the Core, the LaravelUi5 SDK.
This is not an unusual shape. Many mature ecosystems draw a similar line, an open, descriptive foundation paired with a more opinionated layer that operationalizes it, sometimes open, sometimes commercial, often both.
What matters is not the licensing model, but the boundary.
Once such a split exists, it can no longer be treated as an organizational detail. It becomes an architectural commitment that has to be honored continuously. In code, in releases, and in how responsibilities are drawn.
Without that discipline, an SDK would inevitably start to drift, inventing concepts, compensating for missing information, or quietly reaching across the boundary it was meant to respect.
LaravelUi5 chose to take that boundary seriously.
Once the registry was accepted as the single source of truth, and descriptors as stable domain objects, it became impossible to pretend that everything belonged in the same layer. Some code defines reality. Other code can only consume it.
That distinction is what Core and SDK represent.
Core exists to describe what is. Which artifacts exist, how they are categorized, how they relate, and how their metadata is discovered. The SDK exists to work with that description. To derive navigation, dashboards, runtime APIs, and user experience from it.
The SDK does not invent.
It does not guess.
And it does not read UI5 source files directly.
These constraints may sound limiting. In practice, they are clarifying.
Once introspection is confined to Core, and the SDK is forced to consume compiled, stable knowledge, an entire class of problems disappears. Runtime behavior becomes predictable. Testing becomes tractable. Releases become explainable.
This article explores how that boundary is designed, enforced, and tested in practice, and why, in systems like this, separation is not a liability but a form of architectural honesty.
The Tension
At first glance, the split between Core and SDK looks like a simple organizational choice.
Two repositories.
Two release cycles.
A boundary that has to be maintained.
Anyone who has worked in such a setup knows that this creates tension. There is no need to spell it out.
What matters is where that tension is allowed to resolve.
Once the architecture of LaravelUi5 clarified itself, Core and SDK could no longer be treated as peers. They play fundamentally different roles.
Core exists to define the domain. It names artifacts, establishes their identity, and describes how their metadata is discovered and represented. It is descriptive by nature.
The SDK, on the other hand, is consumptive. It does not define what exists. It derives behavior, navigation, and runtime affordances from what Core already knows.
That difference is not philosophical.
It has practical consequences.
The moment the SDK is treated as a consumer of Core rather than a collaborator, the boundary changes character. It stops being a line of separation and starts behaving like an interface.
And interfaces, when taken seriously, create pressure in exactly one direction.
They force the producer to be explicit.
If the SDK cannot rely on implicit knowledge, Core must provide stable descriptors. If the SDK cannot guess paths, Core must resolve identity. If the SDK cannot read files, Core must expose meaning.
In that sense, the SDK becomes the most demanding customer Core will ever have. Not because it needs more information, but because it is not allowed to reach behind the interface to obtain it.
This pressure is intentional.
By forcing all domain knowledge to flow through a single, explicit contract, Core becomes more valuable to every consumer. Commands, tooling, build steps, and future integrations all benefit from the same clarity.
The tension does not disappear.
But it changes its role.
It no longer manifests as drift or ambiguity.
It becomes a signal.
Whenever the SDK cannot proceed without guessing, it points directly at a missing concept in Core.
What follows from this tension is not a compromise, but a rule. A clear statement of responsibility that decides, once and for all, who is allowed to define reality.
That rule is where the architecture starts to hold.
The Rule
Once the boundary between Core and SDK is treated as an interface, the question is no longer whether rules are needed. The only question is which one matters most.
In LaravelUi5, that rule is simple: Core defines reality.
This does not mean that Core is more important in a hierarchical sense. It means that Core is the only place where the system is allowed to decide what exists.
Artifacts.
Their identity.
Their type.
Their descriptors.
The strategies by which their metadata is discovered.
All of that belongs to Core – and nowhere else.
The SDK operates entirely downstream of those decisions. It consumes what Core provides and derives behavior from it. Navigation, dashboards, runtime APIs, and user experience are not invented in the SDK. They are interpretations of an already defined model.
This distinction is subtle, but decisive.
The moment the SDK is allowed to compensate for missing information, the boundary collapses. What begins as a small convenience quickly turns into parallel knowledge that is neither validated nor shared.
By enforcing the rule that Core defines reality, LaravelUi5 avoids that drift by design.
If something is missing, it is not patched in the SDK. It is added to the domain model. If a concept cannot be expressed through descriptors, the problem is not in the consumer. It is in the producer.
This rule also clarifies responsibility.
Core is responsible for being complete, explicit, and stable. It must expose everything a consumer needs.
The SDK, in turn, is responsible for being honest. It must work strictly with what it is given, without inference or guesswork.
That honesty is what makes the boundary valuable.
Because once reality is defined in exactly one place, every consumer benefits. Commands become simpler.
Build steps become predictable.
Runtime behavior becomes explainable.
And the SDK, being the first and most demanding consumer, becomes a proof point. If it can operate without reaching behind the interface, the architecture is holding.
The rule does not remove flexibility.
It localizes it.
Discovery remains flexible inside Core.
Consumption remains stable everywhere else.
This is the point where the split between Core and SDK stops being a structural decision and starts becoming an architectural guarantee.
The Boundary
Once the rule is clear that Core defines reality, the next question follows naturally.
If the SDK is not allowed to invent or infer, where does the information it consumes actually come from?
The tempting answer would be: wherever it is most convenient.
From files on disk.
From manifests.
From whatever happens to be present at runtime.
That answer is precisely what the boundary is meant to prevent.
Introspection is not just a technical activity.
It is an act of interpretation.
It decides which sources are authoritative, how conflicting information is resolved, and which details are promoted to domain knowledge.
Those decisions define reality.
And that is why they belong in Core.
The SDK certainly needs introspected knowledge. It needs to understand navigation intents, routes, targets, i18n scopes, and artifact relationships. But needing information is not the same as being allowed to discover it.
Discovery implies context.
Filesystem layout.
Development stage.
Source availability.
None of these are properties the SDK can safely depend on.
If the SDK were to read UI5 source files directly, it would immediately inherit assumptions about paths and environments. It would start treating accidental properties of the workspace as if they were part of the domain. And in doing so, it would quietly reintroduce invention, just under a different name.
LaravelUi5 avoids that by drawing a strict line.
The SDK is intentionally blind to UI5 source code.
It does not parse manifest.json.
It does not inspect XML views.
It does not traverse directories.
Instead, introspection happens earlier, inside Core, where context is available and variability can be handled explicitly.
But that does not mean the SDK talks to Core through a narrow or opaque channel.
Quite the opposite.
As part of this clarification, the domain interface itself became richer and more explicit.
Where the boundary was previously implicit, it is now made explicit through strengthened contracts on Ui5AppInterface and Ui5LibraryInterface. The introduction of the getSource() method provides a controlled entry point for source introspection, without exposing filesystem details to consumers. This design eliminates the leakage of path-based knowledge and allows the domain to describe how a source can be resolved, while deliberately abstracting away where that source happens to live.
With the introduction of getSource(), the boundary stops being conceptual and becomes tangible.
The method is intentionally narrow. It does not expose files, paths, or discovery mechanics. Instead, it opens a single, controlled door into the result of introspection.
Calling getSource() on a Ui5AppInterface does not reveal where an application lives. It yields a domain object that represents what Core knows about that application’s source after introspection has already taken place.
This is the point where consumers step through the door.
// Ui5AppSource.php
public function getSourcePath(): string;
public function getPackageMeta(): ?Ui5PackageMeta;
public function getFramework(): ?Ui5Framework;
public function getDescriptor(): Ui5AppDescriptor;
public function getBootstrap(): ?Ui5Bootstrap;
public function getI18n(): Ui5I18n;
public function isDev(): bool;On the other side, they do not find a filesystem or a manifest file. They find a structured API: an object that exposes routes, targets, i18n scopes, and other source-derived facts as explicit, typed information. Everything visible here is intentional. Everything implicit has been resolved earlier.
The same applies to libraries through Ui5LibrarySource. Both types make “source” a first-class domain concept, not something to be discovered again, but something to be relied upon.
By returning domain objects instead of raw data, the interface makes a clear promise: consumers are allowed to use the result of introspection, but they are not allowed to repeat it. The door opens exactly once, and only in one direction.
The introduction of explicit contracts, richer interfaces, and dedicated DTOs was not an internal cleanup. It was a direct consequence of taking the boundary seriously. Once Core is treated as a producer with real consumers, its interface must be intentional. And whenever the SDK cannot proceed without guessing, that is no longer a problem to work around, but a signal pointing directly back to the domain model itself.
The Proof
Once the boundary between Core and SDK is made explicit, it stops being a design idea and starts becoming something that can fail.
And that is precisely what makes it testable.
Testing across this boundary is not about asserting individual behaviors. It is about verifying that responsibilities are honored and that neither side quietly compensates for the other.
This becomes especially important in a dual-repository setup. With no shared internals and no implicit guarantees, tests are the first place where architectural shortcuts would surface.
One of the first constraints this introduces is deceptively simple: there are no real workspaces in tests.
The SDK cannot rely on a checked-out UI5 project.
Core cannot assume a particular filesystem layout.
Absolute paths are meaningless.
Anything that depends on “where things live” has to be expressed explicitly, or it cannot be tested at all.
That pressure is intentional.
Instead of testing against repositories, LaravelUi5 tests against fixtures. Minimal, declarative representations of domain scenarios that exercise the contracts, not the environment. What matters is not how something was discovered, but what the domain model exposes once discovery is complete.
Orchestra’s TestBench plays an important role here. By controlling the application base path explicitly, tests can simulate consumption scenarios without recreating a full workspace. This keeps tests focused on behavior at the boundary, not on incidental setup.
Another consequence of the explicit boundary is that conventions themselves become testable. If the SDK relies on a certain shape of descriptors or DTOs, that shape must be asserted. If a source strategy is expected to behave differently in different contexts, that difference can be verified in isolation.
Nothing needs to be inferred.
Nothing needs to be guessed.
This is where the value of the separation becomes tangible.
If the SDK cannot be tested without reaching into Core’s internals, the boundary is too weak. If Core cannot evolve without breaking SDK tests, the contract is too vague.
The tests sit exactly in between.
They don’t just protect behavior, they protect intent.
A simple rule emerges from this:
If you can’t test the boundary, you don’t own it.
In LaravelUi5, that rule applies in both directions.
Core is tested as a producer of stable domain knowledge.
The SDK is tested as a consumer that must remain honest about what it receives.
The result is not a larger test suite.
It is a clearer one.
Tests stop asserting incidental details and start asserting architectural promises. And whenever a test becomes hard to write, it is usually not a testing problem, but a signal that the boundary itself needs clarification.
The Contract
A boundary that holds under test still has to hold over time.
In a system split across Core and SDK, versioning is where that promise is either kept or broken. Not through dramatic failures, but through small, incremental decisions that accumulate.
LaravelUi5 treats versioning as a continuation of the same producer–consumer relationship established earlier. The rules do not change once code is released. They become more important.
Core moves first.
When the domain model evolves, it does so in Core. New artifact types, new descriptors, or changes in interpretation are introduced there explicitly and deliberately. Only once those changes are published does the SDK follow, adapting its behavior to the updated reality.
This ordering is not procedural.
It is architectural.
Because Core defines reality, breaking changes can only originate there. If the SDK were allowed to introduce breaking changes independently, it would implicitly redefine the domain, thus violating the very boundary it is meant to respect.
That discipline has a stabilizing effect.
Consumers of the SDK are not required to track subtle shifts in behavior. They do not need to guess whether an update merely adds capabilities or silently changes assumptions. Breaking changes are localized, visible, and attributable.
Predictability becomes the primary feature.
This also affects how releases are perceived internally. SDK releases are not opportunities to “fix” Core decisions after the fact. They are opportunities to respond to them and to interpret newly available domain knowledge in a consistent way.
When that relationship is honored, trust accumulates naturally.
The SDK earns trust by being reliable.
Core earns trust by being explicit.
And the boundary between them remains intact, not because it is enforced mechanically, but because it is respected conceptually.
Versioning, in this sense, is not about numbers.
It is about continuity.
As long as Core continues to define reality, and the SDK continues to consume it honestly, the system can evolve without surprising its users.
That is what allows two repositories to move at different speeds without drifting apart.
The Leverage
Clearing the boundary between Core and SDK did more than prevent drift. It forced LaravelUi5 to name its domain explicitly.
Once Core was no longer allowed to rely on implicit knowledge, it had to become precise: about artifacts, descriptors, and the meaning of sources. Once the SDK was no longer allowed to compensate, it had to become honest: about what it knows and what it does not. In that relationship, truth stopped being an aspiration and became a requirement.
This is what gives the architecture leverage.
It marks a clear shift in the LaravelUi5 journey.
From merely enabling UI5 to run inside Laravel, to making UI5 understandable and therefore exploitable as a domain. New capabilities no longer need special handling or hidden conventions. They attach to a model that is already explicit, tested, and shared.
Two repositories did not slow this down.
They made it unavoidable.
By institutionalizing the boundary, LaravelUi5 ensured that every gap in understanding becomes visible, and every missing concept has exactly one place to live. The SDK, as the first and most demanding consumer, turns that visibility into continuous feedback.
In that sense, two repositories force you to tell the truth —
not once, but as an ongoing condition for progress.