Skip to content

App Backends Contract

Everything in this page is exported from @hellmai/lumenflow-packs-app-backends/contract (and re-exported from the package root). Each type is a zod schema with an inferred TypeScript type, for example ProvisionRequestSchema and ProvisionRequest. Every object schema is strict: an unknown field is a contract violation, not a silent extension.

The contract has two kinds of values.

  • Management read models, AppBackend and Operation. They are wire resources, so their field names are snake_case.
  • SPI value types, everything an adapter takes or returns (ProvisionRequest, BackendHandle, BackendStatus, MigrationBundle, RestorePoint, and so on). Their field names are camelCase.

The split is deliberate and the only place the two conventions meet. A caller that renders an AppBackend maps from SPI values in one place; an adapter never sees a snake_case field.

FieldMeaning
backend_idOpaque reference to the backend.
workspace_id, tenant_subject, app_idThe isolation key.
environmentproduction or preview:<name>.
regionThe region triple.
adapter_idThe adapter that holds the backend.
placementOne of the placement values, or null.
stateA backend state.
data_url, auth_urlEndpoints, or null before the data plane serves the backend.
publishable_keylf_pub_ followed by an opaque identifier, or null. See below.
capabilitiesThe adapter capability document.
recovery_window, usageRead models, each null when unavailable.

AppBackend carries no secret. The only key that reaches an application is the publishable key. It selects a backend and maps to the anonymous role; it is an opaque, rotatable identifier, not a bearer token.

Every mutating call is asynchronous and yields an Operation that is polled or signalled: operation_id, kind (one of the SPI operation names), backend_id (or null), state (queued, running, succeeded, failed), error, created_at, updated_at. The schema enforces that a failed operation carries a typed error and that no other state does.

State and capability enums are closed. A value outside the list is rejected, so a typo or an undeclared value never passes for a real one. Where a provider legitimately has a value of its own, the enum is an extensible enum: it accepts the listed values and any value of the form x- followed by a lowercase letter or digit and up to 62 more lowercase letters, digits, ., _ or -. EXTENSION_PREFIX and isExtensionValue are exported for adapters and callers. Extension values are provider-specific by definition.

EnumValuesExtensible
BACKEND_STATESprovisioning, ready, degraded, read_only, restoring, deprovisioning, deleted, failedno
OPERATION_STATESqueued, running, succeeded, failedno
ACCESS_MODESread_write, read_onlyno
CONNECTION_ROLESdata_api, auth, migratorno
ROTATION_TARGETSthe connection roles, plus operator and signing_keyno
EXPORT_FORMATSneutral-jsonl-v1, postgres-custom-dumpno
PLACEMENTSshared-cell, dedicated, provider-project, externalno
PITR_SCOPESbackend, cellno
LEDGER_OUTCOMESapplied, failedno
LIST_SCOPESisolation, adapterno
PRIVILEGEStenant, operatorno
SEALED_PURPOSESthe connection roles, plus export_sink and import_sourceno
ENGINE_FAMILIESpostgresyes
DATA_API_SEMANTICSpostgrest-compatible-v1yes
AUTH_SERVERSgotrue-compatible-v1yes
SIZE_CLASSESsmall, medium, largeyes
RESTORE_POINT_REASONSpre_migration, on_demand, scheduledyes

Every backend belongs to exactly one IsolationKey: { workspaceId, tenantSubject, appId }. Each is a non-empty identifier of at most 256 characters with no control characters.

The isolation key is the unit of tenancy for the whole SPI. Every operation that takes a handle checks that the caller’s isolation key owns it and returns not_found otherwise, so a caller cannot tell an absent backend from someone else’s. list is scoped to the caller’s isolation key. Idempotency records are scoped by it too (see Adapter SPI).

Region is { residency, provider, location }.

  • residency is a neutral residency code (lowercase letters, digits and _, 2 to 32 characters). The contract fixes only its shape; the set of accepted codes belongs to whoever hosts the backend.
  • provider and location are the adapter’s own vocabulary.

An adapter declares the regions it can serve in its capability document. A provision request for a region the adapter did not declare is refused with unsupported_capability.

production, or preview:<name> where <name> is lowercase letters, digits and -, up to 63 characters.

A BackendHandle is { backendId, adapterId, placementRef, dbRef, region }, plus an optional adoption: { mode } on a backend registered with adopt (a database the pack did not create). backendId, placementRef and dbRef are opaque references: 8 to 128 URL-safe characters that carry no workspace, tenant or app identifier. This matters because catalog names in a shared database server are readable by every connected role. Adapters derive database and role names from random references, never from an isolation key.

No operation returns a credential in clear. Where a credential or a location of data must cross the SPI, the contract uses a sealed reference: { custodyRef, purpose, expiresAt }, resolvable only inside the data plane or custody service that issued it.

TypepurposeReturned or taken by
SealedConnectionRefdata_api, auth or migratorconnectionFor returns
SealedSinkRefexport_sinkexportSnapshot takes
SealedSourceRefimport_sourceimportSnapshot takes
SealedAdoptionRefadoption_sourceadopt takes

Auth provider client secrets follow the same rule: AuthProviderSettings carries clientSecretRef (an opaque reference) and never the secret.

GroupTypes
ProvisioningProvisionRequest, AuthSettings, CorsSettings, BackendHandle
StatusBackendStatus, UsageReport, RecoveryWindow, BackendLimits, CapacityReport
MigrationsMigrationBundle, MigrationPlan, MigrationLedgerEntry, ConsumerSignature
RestoreRestorePoint, RestoreTarget (point_in_time or restore_point), VerificationReport, SwapResult
Export and importExportManifest, ImportReport
OperatorListFilter, ListPage, CancelResult, ReconcileReport

A MigrationBundle is a bundleId plus 1 to 1000 migrations, each with a version, a name, the sha256 of its SQL and the SQL text. A bundle may carry a detached consumerSignature (ed25519, with key id, expiry and nonce); the adapter carries it and verification belongs to the operator. VerificationReport.equal is true exactly when every table and sequence is equal and the privilege diff is empty; the schema enforces that, and algorithmVersion names the table-hash algorithm (VERIFY_ALGORITHM_VERSION is chained-sha256-v1: counts plus chained SHA-256).

AdapterCapabilities is what an adapter declares about itself. Key fields:

  • engineFamily, engineVersions, dataApiSemantics, authServer.
  • rowLevelPolicies: false means the adapter cannot host the policy contract. See Conformance for what that costs.
  • transactionalMigrations, restorePoints, branching, crossRegionBackups.
  • pitr: { supported, maxWindowDays, granularitySeconds }. When supported is false both numbers must be 0; when true both must be greater than 0. pitrScope is backend or cell.
  • exportFormats (no repeats), regions, placement, extensions (the allowlist the adapter enforces).
  • maxBackends (null means unbounded), maxConnectionsPerBackend, maxDatabaseSizeGb.
  • coldStartP95Ms: measured, never guessed. null means not measured.

capabilitiesVersion is CAPABILITIES_VERSION (currently 1) and changes on any breaking change to the document shape.