Fix boundary violations
Resolve a rejected Core or cross-App import by publishing the smallest neutral SDK contract instead of adding an exception.
Package boundary violations
This reference is for Nexia maintainers who already have authorized access to the private host environment. Core is not distributed to external developers. These host commands are not prerequisites for the public CLI and cloud sandbox; start with the quickstart.
The coupling ratchet rejects a reference from an App Package into the platform
or another App. A clean App has no baseline file; if a pre-existing exception
remains, it permits only that App's recorded file-and-symbol pair in
.nexia/coupling-baseline.json.
First find an existing capability in SDK contracts. Replace the rejected import with that public contract. If the behavior belongs to the App, keep it there; only a missing platform responsibility warrants a new SDK surface.
Symptom index
| Symptom | Section |
|---|---|
| Ratchet rejects a new file-and-symbol pair | A PHP import into App* |
Module not found: @/…, @shell/…, @shared/ui | A frontend platform alias import |
| An import of another App's class | An App-to-App import |
| A local SDK change has no runtime effect | The local SDK is not selected |
Target [Nexia\…] is not instantiable. | The contract exists but is unbound |
| The capability I need has no SDK contract | No contract exists yet |
| A baseline file seems to prove the pattern is fine | Baseline is not precedent |
A PHP import into App*
New PHP app-to-core coupling detected.
Cause. An App Package imported a platform implementation class. PHP App code may
import host-facing contracts only from Nexia\*.
Fix. Find the published contract for the capability:
| You reached for | Import instead |
|---|---|
| A platform authorization service | Nexia\Laravel\Access\Contracts\ResourceAuthorization |
| Actor or Party lookup | Nexia\Identity\Contracts\ActorDirectory, Nexia\Laravel\Identity\Contracts\PartyDirectory |
| Legal Entity or Operating Unit lookup | Nexia\Organization\Contracts\OrganizationDirectory |
| Tenant context restoration | Nexia\Tenancy\Contracts\TenantRunner |
| Event publication | Nexia\Events\Contracts\Outbox |
| Command Palette commands | Nexia\Palette\Contracts\PaletteCommandContribution |
| Searchable Resource records | Publish the Resource model and mark fields searchable: true in resourceListFields(); Core owns the governed Search allowlist and authorization path |
| A host identity or organization reference | ActorDirectory, PartyDirectory, or OrganizationDirectory |
Resolve from the container so the platform supplies the implementation:
use Nexia\Laravel\Access\Contracts\ResourceAuthorization;
// $query is the generated Note model query inside the authenticated tenant request.
$query = app(ResourceAuthorization::class)->scopeVisible($query, 'workshop.note');Confirm. The ratchet passes:
php scripts/coupling-ratchet.php check qualityThe doctor does not check coupling — it covers discovery, registration, permissions, frontend entry, and translations.
Prevent. Check the namespace map in SDK contracts before writing an import.
A frontend platform alias import
Module not found: Can't resolve '@shell-primitives/resources/list'
Cause. App frontend code resolved an import into the Core source folder. Forbidden
prefixes: @/, @core/, @hooks/, @lib/, @shared/, @shell/,
@shell-primitives/.
Fix. Import the same symbols from the SDK:
import {
ResourceTable,
ResourcePrimaryCell,
NxButton,
api,
can,
useResourceListParams,
} from "@nexia/sdk/host";The SDK exports the layout primitives, form controls, table components, data hooks, inspector surfaces, and the Approval composer registry. The commonly used ones: React components and hooks.
Confirm. The ratchet is what rejects a platform alias — the dependency validator skips local and alias specifiers and never sees them:
php scripts/coupling-ratchet.php check qualityPrevent. Follow Build an App screen for supported imports. A historical baseline entry permits only its recorded file-and-symbol pair; it is not evidence that current generators should emit Core aliases.
An App-to-App import
An App Package imports a class or frontend module from another App Package.
Cause. An App imported another App's class, or declared it as a package dependency. App-to-App imports and package dependencies are forbidden, with no exception.
Fix. Choose by what you actually need:
| Need | Mechanism |
|---|---|
| Read another App's record now | Nexia\ResourceReference — store the Resource Key and public_id, let Core resolve it |
| React to another App's change | Nexia\Events — the platform invokes the consumer inside the tenant; only delegated listeners restore the user and recheck authority |
| Share a capability | A new app-neutral SDK contract, bound by the platform |
A ResourceRef names a record without importing its model, so the owning App keeps
control of exposure and the actor's authority is applied on resolution.
Confirm. The ratchet passes and no packages/* requirement names another App.
Prevent. Do not create a competing source of truth. Use authorized Resource References for live values; keep historical snapshots or event projections only when the owner contract explicitly supports that use.
The local SDK is not selected
A local SDK edit has no effect after the host reloads.
Cause. A checkout may exist without being in the persisted local package selection. The host then uses the release package declared by its committed lock.
Fix. From the Core root, add the SDK source through the supported helper:
task apps:add PACKAGES=app-sdk
task apps:status -- --offlineThe add task prepares the Composer overlay, frontend local lock, SDK package metadata, and frontend dependency validation. App manifests and imports use the published amuzcorp/nexia-app-sdk-laravel and @nexia/sdk identities.
Confirm. task apps:status -- --offline lists app-sdk, and git -C packages/app-sdk rev-parse --show-toplevel returns the independent SDK repository.
Prevent. Change a persisted selection only with task apps:add or task apps:remove. Runtime tooling resolves installed package paths through Composer\InstalledVersions::getInstallPath() and the generated App map.
The contract exists but is unbound
Target [Nexia\Laravel\Access\Contracts\ResourceAuthorization] is not instantiable.
Cause. The SDK publishes the interface, but the platform has no binding for it in this context. The SDK carries no host-owned implementation by design — an unbound interface means the platform binding is missing, not that the SDK is incomplete.
Fix. This is a host problem, not an App problem. The platform binding is missing or not registered for the current context. Do not work around it by instantiating a platform class.
Confirm. The contract resolves from the container in a normal request.
Prevent. Follow the contract order: publish the SDK surface, make its commit available, bind it in Core, then consume it. Skipping the binding step produces exactly this error.
No contract exists yet
The App needs host behavior, but no app-neutral Nexia\* contract exposes it.
Cause. Some capabilities are genuinely host-only today:
| Capability | Host-only class |
|---|---|
| Site Configuration | App\Settings\SiteConfigRegistry |
| Installation initializers | App\AppRuntime\TenantAppInitializerContribution |
Global record search is not a per-App Palette contribution. Publish a Resource
model with searchable fields in resourceListFields(); Core's governed Search runtime decides
whether the Resource is eligible, obtains candidate identifiers, and reloads
every result through tenant-database visibility and Policy checks. Do not invent
a Palette-specific query contract.
Fix. Either model the need inside your App, or add the contract:
| Situation | Approach |
|---|---|
| App configuration | Model it as an App Resource behind a settings destination |
| Required rows at install | Ship them in a guarded tenant migration |
| Genuinely platform-wide | Publish the smallest app-neutral SDK contract and bind it in the platform |
When adding a contract, keep it minimal. Prefer a one-method interface over exposing a platform Eloquent model — a model leaks schema and turns every internal change into a breaking one. Never copy a platform implementation into the SDK.
Confirm. Your App imports only Nexia\* and the ratchet passes.
Prevent. Check availability before designing around a capability. The Extension Model lists what an App Package may and may not implement. Governed Search Runtime explains the separate record-search path.
Baseline is not precedent
The coupling ratchet rejects a new file-and-symbol pair beside an existing exception.
Cause. Reading .nexia/coupling-baseline.json as a list of approved
patterns. It is an explicit record of an exception that still needs removal,
not precedent.
Fix. Find an existing public contract first. If none fits, decide whether the behavior belongs in the App or expresses a durable platform responsibility. A baseline entry alone does not justify adding an SDK API. See SDK contracts.
Confirm. Your new code imports only Nexia\* and
@nexia/sdk, and the baseline did not grow.
Prevent. Run all three before committing. Only the first covers the import boundary; the other two cover compatibility declarations and the dependency graph:
php scripts/coupling-ratchet.php check quality
task artisan -- nexia-apps:validate-app-compat --app=quality --require-declarations
task artisan -- nexia-apps:validate-app-frontend-dependencies --app=quality --require-manifestsRelated
- SDK contracts — every published PHP namespace
- React components and hooks — every published frontend symbol
- App SDK — the fixed order for adding a contract
- Nexia System Overview — the ownership rules behind the ratchet