Skip to content

Database Schema ​

The SDK ships 19 migrations creating 22 tables, all prefixed sdk_. They load from the package — php artisan migrate applies them and there is nothing to publish.

One table the SDK does not create is users. The SDK ships no auth table and makes no assumption about yours; the link is a partner_id column you add to your own table. See Installation.

The invariants that shape everything ​

Four rules run through the whole schema. Knowing them means you can predict a table's shape before you read it.

1 · Every grant is time-bound, and no timestamp is nullable. Every grant table carries valid_from (defaults to now) and valid_until (defaults to 9999-12-31 23:59:59). There is no "forever" and no "unset" — a grant that has ended carries the moment it ended, which is what makes ui5:explain --at= able to answer questions about last Tuesday. See time-aware grants.

2 · Every grant records who granted it. assigned_by_partner_id on every assignment table, with a restricting foreign key: a partner who has granted something cannot be deleted out from under the audit trail.

3 · Partner identity is enforced inside the SDK. Every partner_id in an sdk_* table is a strict foreign key to sdk_partners.id, cascading on delete. The one nullable, soft link in the system is the users.partner_id bridge in your own table.

4 · Catalog tables are owned by code. sdk_partner_roles, sdk_partner_relationship_types and sdk_address_providers are Customizing catalogs: ui5:sync reconciles them to exactly what your attributes declare, and deletes rows you did not declare. They carry a surrogate id plus a unique business code, and no timestamps — the worker writes through the query builder, not Eloquent.

The registry catalog ​

What ui5:sync writes from your declarations.

sdk_artifacts — one row per registered artifact. namespace (unique), type (the ArtifactType enum), version, expose, title, description. No foreign key to a module: modules are runtime-only, artifacts are addressed by namespace string.

sdk_abilities — artifact_id, ability, type (the AbilityType enum). Unique per (artifact_id, ability, type). Every ability belongs to exactly one artifact.

sdk_roles — role (unique) and a nullable scope. sdk_ability_role links roles to abilities.

sdk_groups — code (unique) and a description. sdk_group_role and sdk_ability_group link a group to roles and to abilities directly, which is what makes a group a single grantable bundle of both.

sdk_slots — the slot catalog: name (unique), type, default_value (JSON), note, editable, a declared_by_snapshot, and synced_at / removed_at. The removed_at column is why a retired slot leaves a tombstone rather than vanishing.

Security assignments ​

Three tables, one shape. Each grants one thing to one partner for one window.

TableGrantsUnique on
sdk_ability_assignmentsone ability(partner_id, ability_id, valid_until)
sdk_role_assignmentsone role, optionally in a context(partner_id, role_id, context_type, context_id, valid_until)
sdk_group_assignmentsone group(partner_id, group_id, valid_until)

valid_until is part of every unique key, which is what lets the same grant be issued again for a later window without colliding with its own history.

sdk_role_assignments carries the extra pair context_type / context_id — that is role scope: this role, but only for that project. It has its own composite index for the scope lookup, because that query runs on every scoped read.

sdk_delegations is the impersonation table: delegate_partner_id, target_partner_id, granted_by, a note, and the same validity pair. See impersonation.

Settings ​

sdk_settings carries a scoped value and quite a lot of metadata, because a setting has to be editable by a console that has never seen your code:

  • artifact_id — whose setting it is;
  • scope (the Scope enum: Platform, Installation, Tenant, Site, User) and setting — the key;
  • partner_id — set only on a User-scope row;
  • set_by — the writer, the system actor on a seeded row;
  • value (JSON) and value_type — validated separately from storage;
  • level — the edit level shown as a badge;
  • value_help, value_help_scope, model_class — how the console offers a value for a Model-typed setting.

sdk_slot_assignments is the actor layer: (partner_id, slot_name) unique, a JSON value, and assigned_by_partner_id. One value per actor and slot — a write replaces, a clear deletes, and there is no history. See actor slot values.

Partners ​

sdk_partners is the business actor — person, organisation or department in one table. It carries the identity fields (ext_ref, type, system_level, search_term, name, first_name, avatar_path, gender), the contact fields (email, phone, mobile), the legal-identity block (vat_number, company_registration_number, registered_office), the dates (foundation_date, liquidation_date, birthdate), language_code, and archived.

search_term is an internal matchcode — upper-cased and stripped of punctuation for searching. It is not a display field, and an export that includes it reads as noise.

sdk_partner_roles (catalog) and sdk_partner_role_assignments are the business role axis, separate from the security roles above: who is a customer, a supplier, a key account. The assignment carries context_type / context_id and an is_primary flag, and is unique per (partner, role, context).

sdk_partner_relationship_types (catalog) and sdk_partner_relationships hold the graph between partners — employment, ownership, a named function. The type carries both a directional_name and a reverse_name, so one row reads correctly from either end.

sdk_partner_profiles holds the many-valued online profiles — website, LinkedIn and the other platforms of its type enum — as type / value / label / sort; value is always a full URL, never a bare handle. (sdk_slot_assignments is not a grant table and has no validity window.)

sdk_partner_addresses and sdk_address_providers are the address domain. Three invariants live in the model rather than the schema, because MySQL cannot express any of them: at most one Primary row per partner (no partial unique index), the structured-or-free-text capture rule, and the coordinate pairing — both columns or neither, and a row with no postal address must carry them (no conditional NOT NULL for either).

Reading the whole thing at once ​

bash
php artisan migrate:status          # what has run
php artisan db:table sdk_partners   # one table's real shape in your database

The migrations themselves are the authority, and they are commented: each one opens with why the table is shaped the way it is, and which invariants the schema could not carry. If you are about to write a query against an sdk_* table, read its migration first — the comment usually answers the question you were about to ask.

What not to do ​

  • Do not write to a Customizing catalog. ui5:sync will delete what you insert. Declare it in code.
  • Do not insert a grant with a null validity. Use the defaults; nothing in the engine expects a null and the unique keys depend on valid_until.
  • Do not write to sdk_partner_addresses with DB::table(). You bypass all three model guards. The same caution applies to sdk_partner_relationships.
  • Do not add columns to an sdk_* table. They belong to the package and a future migration may touch them. Your own data belongs in your own tables, joined on partner_id.