The Core seam
The Core seam is the membrane between Core and the SDK. Core is stateless: it knows about artifacts, routing, and slots, and it runs perfectly well on its own. The SDK is stateful: it adds tenant, actor, abilities, and time. The seam is where the two meet; its classes live under LaravelUi5\Sdk\Platform.
One sentence fixes the surface. The SDK plugs into Core at a fixed set of extension points and enriches the loaded registry and runtime context with state — it never writes state back into Core. The dependency arrow is SDK → Core, always.
The two registries
The SDK ships two registry implementations, and choosing between them is the host's call, made once in config('ui5.registry'):
| Registry | When | How it works |
|---|---|---|
SdkRegistry | development | Extends Core's Ui5Registry; discovers roles, abilities, customizing catalogs, the Concept/Weave graph, value helps, and intents live on each request via reflection. Zero cache-staleness; pays the reflection cost every request. |
CachedRegistry | production | Reads the precompiled bootstrap/cache/ui5.php. Maximum performance, no reflection. Built by ui5:cache from SdkRegistry::exportToCache(). |
// config/ui5.php — development
'registry' => \LaravelUi5\Sdk\Platform\SdkRegistry::class,
// production: swap to the cached twin and run `php artisan ui5:cache`
'registry' => \LaravelUi5\Sdk\Platform\CachedRegistry::class,This is a deliberate pair, not duplication waiting to be deduped. Both are public contracts: a consumer consciously binds one. Their cost is that the export and read shapes must stay in lock-step — and cold-boot parity is a hard requirement: the live and cached registries must return the same runtime-visible surface.
How the SDK plugs into Core
The seam with Core is exact and sanctioned. Core's Ui5Registry declares a protected afterLoad(array $config) hook and calls it after loading. SdkRegistry overrides it to run its enrichment passes, reading Core's protected registry state ($modules, $artifacts, $slots, …). That hook plus those protected members are the Ui5Registry ↔ SdkRegistry contract. It is Stable (soft-freeze): changes are additive and announced. Enrichment layers over Core's loaded registry and never mutates it.
SdkContext is the runtime counterpart. Core's Ui5ContextInterface demands only artifact() + locale(); everything else on SdkContext is an SDK-owned public contract that providers and handlers depend on:
$context->actor(); // the acting Partner
$context->principal(); // the authenticated Partner (≠ actor under impersonation)
$context->tenant(); // the operating organization
$context->at(); // the request timestamp (time-bound authz)
$context->abilities(); // the actor's AbilitySet
$context->setting('key'); // a scope-resolved setting of the artifact the request addresses (null if undeclared)
$context->appSetting('key'); // a scope-resolved setting of that artifact's app (null if undeclared)
$context->appSettings(); // every setting of the app, key => value
$context->primaryOrgPartner($at);
$context->orgPartners($at);State flows Core → SDK and stops; nothing writes back into Core.
ui5:sync is mandatory
The registry's role, ability and customizing maps are sync-time projections: inputs for ui5:sync, not a source of authorization. Runtime authorization reads the database (AbilityGrantQuery over the synced sdk_* tables). Two consequences:
- An un-synced install has no grants.
ui5:syncis the act that moves metadata from the registry projection into the database that serves authz. Run it on every deploy. See Sync Pipeline. - The cache carries the runtime surface.
exportToCache()exportsmodules,artifacts(with their class and module indexes),settings,intents,valueHelps,readAbilities,contributedScopes, the Concept/Weave graph (concepts,inboundEntries,outboundEntries) andslots.ui5:cachethen adds one database-derived map,abilities(namespace → type → ability → synced id), which is why it runs afterui5:sync. The sync-only role and customizing maps are deliberately absent.
One context, two binding sites
SdkContext is built exactly once per request by SdkUi5ContextFactory::build() (the host names it as context_factory in config/ui5.php) and is a final readonly object — no setters, no re-resolve. It reaches the request on two paths:
- UI5 routes — Core's
ResolveUi5Contextmiddleware calls the factory. - OData routes — the OData chain has no context-resolution step, so
BindSdkContextForODatainvokes the same factory. It is ordered afterResolveODataEndpoint(which binds theODataServiceInterface) and afterEnsureODataAuthenticated(the factory throws without auth). Without this bridge, scoped OData queries fall through to1 = 0.
Per-actor manifest extensions (suite apps)
An app's manifest.json can be finalized for the acting subject at request time. Core's ManifestController offers a narrow capability — Ui5ManifestExtensionInterface (extend(Ui5ContextInterface $context, array &$extensions): void) — and runs it on the manifest object it already resolves (getLaravelUiManifest()), mirroring its OData dataSources injection. Only the sap.ui5/extends/extensions node is exposed, never the whole manifest. Because the result is per-subject, manifests are served Cache-Control: no-store.
The SDK implements it on AbstractSdkManifest via the GatesViewVisibility trait: it rewrites the SAP-standard sap.ui.viewModifications block — flipping each role-gated control's visible from the actor's See grants (fail-closed) and stripping the server-only role. This is the delivery mechanism for See-type UI visibility (it replaces the context.json see snapshot). Core stays stateless: it offers the seam + the instanceof; all grant resolution lives in the SDK.
Suite membership is the opt-in. Extending
AbstractSdkManifestis how an app joins the suite — it contributes the LeanShell fragment and gates view visibility per actor. A standalone app (e.g. a customer portal) extends Core'sAbstractManifestand gets neither. The base class is the switch — a deliberate developer decision.
Public vs internal
| Public (Stable, soft-freeze) | Internal |
|---|---|
SdkContext; the SdkRegistry / CachedRegistry pair; AbstractSdkManifest (the suite manifest base — shell fragment + the GatesViewVisibility manifest-extension seam); the manifest contracts (AbilityWriterInterface, AbilityNotificationInterface, Ui5ShellContributorInterface); SdkUi5ContextFactory, ShellContextArtifactResolver, BindSdkContextForOData | ManifestAbilityExtractor |
The three context classes are public because a host names them in config/ui5.php.
See also
- Sync Pipeline — the
ui5:syncprojection - Resolution Engine — runtime authz from the DB
- Tenancy — the
tenant()the context carries