ObjectStackObjectStack

Kernel Services Checklist

Complete inventory of ObjectStack kernel services with protocol methods, implementation status, and development requirements.

Kernel Services Checklist

This page is hand-maintained. The per-slot Provider column mirrors CORE_SERVICE_PROVIDER (packages/spec/src/system/core-services.zod.ts), which is the CI-guarded source of truth; the method inventories mirror the per-domain contracts in packages/spec/src/api/protocol.zod.ts. When the two disagree, the code wins. See Plugins & Packages for the full package catalog.

The ObjectStack protocol defines 15 kernel services registered via the CoreServiceName enum (v17 removed the never-implemented graphql entry and retired the never-filled workflow slot, #4451). Each service maps to a set of protocol methods governed by its per-domain contract (DataProtocol, MetadataProtocol, ...) — the transitional ObjectStackProtocol composition alias was dissolved in v17 (ADR-0076 D9); capability availability comes from the runtime discovery services registry.

Key architecture principle: the kernel guarantees only data and metadata, and even those are filled by packages (@objectstack/objectql, @objectstack/metadata) rather than baked in — the kernel's own contribution is an in-memory fallback for the core slots that have one (metadata, cache, queue, job, i18nnot auth). Everything else — including auth and automation — is delivered by plugins. @objectstack/objectql is an example kernel implementation to get the basic API running; production kernels will be rebuilt as separate plugins.

Legend

  • ✅ Implemented — the 17 kernel-provided protocol methods (DataProtocol 9 + MetadataProtocol 8)
  • ⚠️ Framework — the slot is filled by the kernel's in-memory fallback: real reads and writes, no persistence (self-declares degraded, ADR-0076 D12)
  • ❌ Plugin Required — the remaining 33 methods declared across the other per-domain contracts (analytics 2, automation 1, packages 6, views 5, permissions 3, realtime 6, notification 7, i18n 3). Declared is not routed: packages is answered kernel-side by the /packages dispatcher domain over the ObjectQL registry, i18n has a kernel in-memory fallback, and views / permissions have no implementation anywhere — the rest wait on whatever fills the slot. (workflow's 3 methods were the fourth such group; the whole slot retired in v17, #4451.)

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                     Kernel Layer                         │
│  ┌──────────────────┐  ┌──────────────────────────────┐ │
│  │  metadata (✅)    │  │  data (✅)                   │ │
│  │  MetadataPlugin   │  │  ObjectQL example kernel     │ │
│  │  → sys_metadata   │  │  Will be rebuilt as plugins  │ │
│  │  (in-mem fallback)│  │                              │ │
│  └──────────────────┘  └──────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│                    Plugin Layer                           │
│  All other services: analytics, auth, automation,        │
│  ui, realtime, notification, ai, i18n,                   │
│  search, file-storage, cache, queue, job                 │
│                                                          │
│  Discovery API reports availability per service          │
│  (available / degraded / stub / unavailable) so          │
│  clients adapt their UI accordingly                      │
└─────────────────────────────────────────────────────────┘

Service Overview

#Service NameCriticalityMethodsStatusProvider
1metadatacore8✅ Implemented (kernel fallback is ⚠️ in-memory)@objectstack/metadata
2datarequired9✅ Implemented@objectstack/objectql
3analyticsoptional2❌ Plugin Required@objectstack/service-analytics
4authcore✅ Implemented@objectstack/plugin-auth
5uioptional5❌ Nothing fills this slot@objectstack/metadata-protocol/ui/view is served by its protocol service, not by a ui service
6automationoptional1❌ Plugin Required@objectstack/service-automation
7realtimeoptional6❌ Plugin Required (in-process only — no HTTP/WS route is mounted)@objectstack/service-realtime
8notificationoptional7❌ Plugin Required@objectstack/service-messaging
9aioptional❌ Nothing ships in this repo@objectstack/service-ai (Cloud/EE — not installable, so the table entry is null)
10i18ncore3✅ Built-in (in-memory fallback)@objectstack/service-i18n
11file-storageoptional❌ Plugin Required@objectstack/service-storage
12searchoptional❌ Nothing ships
13cachecore✅ Built-in (in-memory fallback)@objectstack/service-cache
14queuecore✅ Built-in (in-memory fallback)@objectstack/service-queue
15jobcore✅ Built-in (in-memory fallback)@objectstack/service-job

The Provider column mirrors CORE_SERVICE_PROVIDER in @objectstack/spec/system — the single table discovery reads when it tells a caller what to install for an empty slot, and the value it reports as provider. Two caveats. The table carries no entry for metadata or data: those are filled by the engine itself, never by an installable optional package, so they need no remedy (NO_REMEDY_SLOTS in the guard script). And null there does not always mean "nothing ships" — it means "no name belongs in an Install X sentence", which covers two cases:

  • Nothing provides the slot at allsearch. Discovery says exactly that rather than naming a plausible package: No implementation ships for the '<slot>' slot — register a service under it to enable. (workflow and the never-real graphql sat here too until both were retired outright in v17, #4451 — a slot nothing fills and nothing consumes is better removed than explained.)
  • A provider exists but cannot be installedai. @objectstack/service-ai registers the slot in objectstack-ai/cloud and is private: true.

Two slots therefore override the generic line with a hand-written REMEDY_DETAIL sentence: ui, because the package named does not fill the slot (Served by the protocol service — register MetadataPlugin (@objectstack/metadata-protocol) to enable), and ai (Provided by @objectstack/service-ai in ObjectStack Cloud/Enterprise — no implementation ships in the open framework). scripts/check-service-providers.mjs fails CI if a name in the table is not a real workspace package, or if a CoreServiceName slot other than data/metadata is missing from it.

Criticality Levels

  • required: System cannot start without this service
  • core: Falls back to in-memory implementation with a warning if missing
  • optional: Feature disabled; the API answers as an empty slot if missing (404, or the domain's 501)

1. metadata Service ✅ Implemented

Service Name: metadata · Criticality: core
Implementation: MetadataPlugin (@objectstack/metadata) — persisted to sys_metadata. Fallbacks: the kernel's in-memory createMemoryMetadata (@objectstack/core) when nothing registers the slot, over ObjectQL's in-memory SchemaRegistry of artifact-loaded metadata.
Route Mounts: /api/v1/meta, /api/v1, /api/v1/packages

Protocol Methods

MethodSignatureStatus
getDiscoveryGetDiscoveryRequest → GetDiscoveryResponse
getMetaTypesGetMetaTypesRequest → GetMetaTypesResponse
getMetaItemsGetMetaItemsRequest → GetMetaItemsResponse
getMetaItemGetMetaItemRequest → GetMetaItemResponse
saveMetaItemSaveMetaItemRequest → SaveMetaItemResponse
deleteMetaItemDeleteMetaItemRequest → DeleteMetaItemResponse
getMetaItemCachedGetMetaItemCachedRequest → GetMetaItemCachedResponse
getUiViewGetUiViewRequest → GetUiViewResponse

Former Gaps — all now closed

Every gap this section used to list has shipped. Only the kernel's own in-memory fallback is still a "framework": it serves real reads and writes but never reaches disk, which is exactly what it self-declares (degraded).

Former gapWhere it landed
DB PersistenceMetadataPlugin (@objectstack/metadata) persists to sys_metadata; only the kernel's in-memory fallback is non-persistent
Multi-instance SyncMetadataClusterBridgePlugin (@objectstack/service-cluster) bridges the metadata.changed channel onto the cluster pub/sub bus, so a mutation on one node invalidates peer registry caches. Needs a cluster service and a metadata service exposing attachClusterPubSub()
Migration / Versioningos diff <before> <after> compares two configurations and flags breaking changes; os migrate plan / os migrate apply reconcile the physical database against metadata
Hot ReloadMetadataPlugin watches its sources (watch) and the compiled artifact (artifactWatch) with chokidar; os dev rebuilds and the server reloads without a restart

Discovery: Per-Service Status

The discovery endpoint returns a services map so clients know what is available. The metadata entry is computed from the implementation that fills the slot, not fixed (#4089): a stack running the kernel's in-memory fallback reports degraded with that fallback's own message, while a stack with MetadataPlugin reports available and no message. Below is the former — the fallback case:

{
  "services": {
    "metadata": {
      "enabled": true, "status": "degraded", "handlerReady": true,
      "route": "/api/v1/meta", "provider": "kernel",
      "message": "In-memory metadata registry — real reads and writes, no persistence (lost on restart). Register MetadataPlugin for a persisted registry."
    },
    "data": {
      "enabled": true, "status": "available",
      "route": "/api/v1/data", "provider": "kernel"
    },
    "auth": {
      "enabled": false, "status": "unavailable",
      "message": "Install @objectstack/plugin-auth to enable"
    }
  }
}

stub vs degraded — and what the dispatcher does with each

A service may self-declare that it is not the full thing (ADR-0076 D12), via a __serviceInfo: { status, handlerReady?, message? } property on the registered instance — see Services → presence is not capability for how an in-process caller reads it. The two values mean different things, and for the HTTP surface the difference is load-bearing (#4058):

DeclaredhandlerReady defaultMeaningDispatcher behaviour
degradedtrueReally does the work, with reduced capability (in-memory, no persistence, no cross-process fan-out)Served normally. Route advertised, requests handled.
stubfalseFabricates its answers — reports success for work that never happenedTreated as an EMPTY slot. Route not advertised, request answered exactly as if nothing were registered (404, or the domain's 501).

Only handlerReady is consulted, never status — so an implementation that declares degraded but explicitly sets handlerReady: false is also treated as empty. The rule is enforced by one predicate (isServiceServeable) shared by the service domains, the route-mount gate, and both discovery builders, so what is advertised and what is served cannot disagree. It applies to the dispatcher-owned domains: /analytics, /automation, /notifications, /ai, /i18n. (/storage is no longer a dispatcher domain — the bridge was retired in #4087 and service-storage mounts /api/v1/storage itself — but its route advertisement is still gated on the same predicate.)

The services map still reports a registered stub as { enabled: true, status: "stub", handlerReady: false } rather than collapsing it to unavailable — "something is in this slot, and it is a fake" says more than "install a plugin".


2. data Service ✅ Implemented

Service Name: data · Criticality: required
Implementation: @objectstack/objectqlObjectQL (IDataEngine)
Route Mount: /api/v1/data

@objectstack/objectql is an example kernel implementation to get the basic data API running. Future production kernels will be developed as separate plugins.

Protocol Methods

MethodSignatureStatus
findDataFindDataRequest → FindDataResponse
getDataGetDataRequest → GetDataResponse
createDataCreateDataRequest → CreateDataResponse
updateDataUpdateDataRequest → UpdateDataResponse
deleteDataDeleteDataRequest → DeleteDataResponse
batchDataBatchDataRequest → BatchDataResponse
createManyDataCreateManyDataRequest → CreateManyDataResponse
updateManyDataUpdateManyDataRequest → UpdateManyDataResponse
deleteManyDataDeleteManyDataRequest → DeleteManyDataResponse

Driver Support

DriverPackageCRUDAggregationReady
InMemory@objectstack/driver-memoryDev/Test
PostgreSQL / MySQL / SQLite@objectstack/driver-sql (Knex)
MongoDB@objectstack/driver-mongodb
SQLite (WASM)@objectstack/driver-sqlite-wasmBrowser/WebContainer

3. analytics Service — Plugin Required

Service Name: analytics · Criticality: optional
Implementation: @objectstack/service-analytics (the only implementation)
Route Mount: /api/v1/analytics — only served when the plugin registers the service

The kernel-level "degraded analytics fallback" (a lightweight ObjectQL adapter registered by the protocol assembly) was retired (#3891): it dropped the caller's ExecutionContext — aggregates ran without RLS/tenant scoping — and silently ignored the contract where filter. Without @objectstack/service-analytics, /api/v1/analytics/* now answers 404 and discovery reports analytics: { enabled: false, status: "unavailable" }.

This holds in dev mode too (#4000): plugin-dev no longer registers an analytics dev stub, and the dispatcher treats a slot filled by any self-declared stub (handlerReady: false, ADR-0076 D12) exactly like an empty one — routes unmounted, request 404. To use analytics locally, install the real engine; @objectstack/service-analytics works against the in-memory driver (via its ObjectQL strategy, or InMemoryStrategy injected from @objectstack/driver-memory), so it needs no database of its own.

And it is no longer analytics-only (#4058 step 2): every dispatcher-owned domain now gates on handlerReady, so a slot occupied by a self-declared stubautomation, notification, ai, wherever one is registered — answers as an empty slot does. The implementations that really do the work in memory declare degraded and keep serving. See stub vs degraded.

Features (via @objectstack/service-analytics)

  • Cube semantic queries: measures / dimensions / where filter → SQL or ObjectQL aggregation
  • Context-aware read scoping: per-object RLS/tenant predicates resolved from the request's ExecutionContext (fail-closed)
  • Cube metadata (/analytics/meta), SQL preview (/analytics/sql), dataset queries (/analytics/dataset/query)

4. auth Service ✅ Implemented

Service Name: auth · Criticality: core
Implementation: AuthPluginAuthManager (@objectstack/plugin-auth, built on better-auth)
Route Mount: /api/v1/auth — only exposed when a plugin registers the auth service

The kernel does NOT handle auth. Install an auth plugin to enable authentication. Without it, the discovery response shows auth: { enabled: false, status: "unavailable" }.

Where each capability lives

The auth service covers authentication (identity). Authorization is not a CoreServiceName slot at all — there is no permission service; the evaluators are registered under their own names (security.permissions, security.rls, security.fieldMasker) by @objectstack/plugin-security.

ModuleAreaShips in
Identity ProviderAuthentication@objectstack/plugin-auth — better-auth sessions + jose JWTs (auth-manager.ts)
User CRUDAuthentication@objectstack/plugin-authadmin-user-endpoints.ts, admin-import-users.ts
Role ManagementAuthorization@objectstack/plugin-security — owns role / permission-set / user-permission-set / role-permission-set objects (ADR-0029 K2)
Permission EngineAuthorization@objectstack/plugin-securitysecurity.permissions (object) + security.fieldMasker (field)
OAuth2 / OIDCAuthentication@objectstack/plugin-auth@better-auth/oauth-provider, @better-auth/sso
Sharing RulesAuthorization@objectstack/plugin-sharingsharing, sharingRules, shareLinks services
Row-Level SecurityAuthorization@objectstack/plugin-securitysecurity.rls (rls-compiler.ts)
Multi-tenancyAuthentication@objectstack/plugin-auth — the tenancy service (tenancy-service.ts, ADR-0093/ADR-0105)
Territory accessAuthorizationNot a separate module: expressed as an RLS dynamic-membership predicate (current_user.territory_account_ids, staged in ExecutionContext.rlsMembership)
SCIMAuthentication@objectstack/plugin-auth@better-auth/scim, gated by OS_SCIM_ENABLED

Spec Files: identity/identity.zod.ts, identity/organization.zod.ts, security/permission.zod.ts, system/auth-config.zod.ts, system/tenant.zod.ts (all under packages/spec/src/)


5–6. Business Services

5. ui Service — 1 routed method ✅ (was 5 declared, none routed)

getUiView

Nothing anywhere registers the ui slot (#4093 / #4146), so CORE_SERVICE_PROVIDER.ui names @objectstack/metadata-protocol rather than a ui plugin: the one route the /ui domain serves is GET /api/v1/ui/view/:object[/:type], which calls getUiView on the protocol service that assembleMetadataProtocol() registers (invoked by ObjectQLPlugin, or by the standalone createMetadataProtocolPlugin()). Without it the domain answers 501 with that remedy spelled out, not a generic "install a ui plugin".

Retired in v17: ViewProtocol's five methods

listViews, getView, createView, updateView, deleteView — and their ten Request/Response schemas — were removed in #6239 under ADR-0049 enforce-or-remove. This checklist had recorded them as declared-and-unrouted since it was written; the removal makes that reading permanent instead of re-derivable. Views are read and written through the surfaces that always served them: the metadata API (/api/v1/meta/view/:name, view being a metadata type) for the stored definition, and getUiView above for the resolved render-time view.

The concrete cost of leaving it declared is on the record: #5948's issue body and its 2026-08-07 maintainer ruling both read GetViewResponseSchema — this retired block, zero implementations — as the contract of GET /ui/view/:object/:type, whose declared response is GetUiViewResponseSchema. One word apart, and identical to a grep.

Retired in v17: the workflow slot

The slot, its IWorkflowService contract and the three WorkflowProtocol methods (getWorkflowConfig, getWorkflowState, workflowTransition) were removed in #4451: nothing ever registered or resolved the slot (ADR-0115 Evidence 5), no method ever had an implementation, and no host ever mounted /api/v1/workflow. The three capabilities it named are live elsewhere — state-machine transitions are an object validation rule of type state_machine, approvals are approval flow nodes on the approvals runtime (ADR-0019 — decisions via POST /api/v1/approvals/requests/:id/{approve,reject,recall}, served by @objectstack/plugin-approvals), and record-triggered automation is lifecycle hooks + record_change flows.

6. automation Service — 1 method ✅ @objectstack/service-automation

triggerAutomation
Trigger engine, event triggers from ObjectQL hooks, flow executor, scheduled triggers. The /automation dispatcher domain gates on isServiceServeable, so a slot filled by a self-declared stub answers as an empty one.


7–10. Communication Services

7. realtime — 6 methods · @objectstack/service-realtime

realtimeConnect, realtimeDisconnect, realtimeSubscribe, realtimeUnsubscribe, setPresence, getPresence

service-realtime is an in-process pub/sub bus, not an HTTP/WS surface. The dispatcher has no /realtime branch and no plugin mounts one, so routes.realtime is never advertised — an advertised route would 404 (ADR-0076 D12, #2462), and features.websockets is hardcoded false for the same reason. These six RealtimeProtocol members are declared and unrouted; when the service is registered both discovery builders report the slot degraded with a message saying the bus is in-process only and no HTTP/WS surface is mounted. Re-advertising waits on a real transport.

8. notification — 7 methods · @objectstack/service-messaging

registerDevice, unregisterDevice, getNotificationPreferences, updateNotificationPreferences, listNotifications, markNotificationsRead, markAllNotificationsRead

The slot name is notification (singular) and the package that fills it shares no word with it — which is why the remedy sentence is looked up in CORE_SERVICE_PROVIDER rather than derived from the slot name. The /notifications domain currently routes the inbox subset: GET /notificationslistInbox, POST /notifications/readmarkRead, POST /notifications/read/allmarkAllRead. All three are optional on INotificationService — a send-only provider (SMTP, Twilio, a webhook) fills the slot legitimately without an inbox, and each route probes its own method and answers 501 when absent.

9. ai — contract removed ❌

aiNlq, aiSuggest, aiInsights

Removed. These three were optional protocol methods (aiNlq? …) that no service in any repo ever implemented — the /ai/{nlq,suggest,insights} routes they backed were mounted by nothing and 404ed for their whole life. In v17 the AiProtocol contract, its schemas, and the client.ai.{nlq,suggest,insights} methods that called them were deleted outright (#3718). The AI service that does exist (service-ai, Cloud/EE) serves a different surface — chat, complete, models, conversations, agents — and client.ai was rebuilt against those real routes, so this entry describes a contract that no longer exists. The ai slot still exists in CoreServiceName, but nothing in this repo fills it (CORE_SERVICE_PROVIDER.ai is null).

10. i18n — 3 methods

getLocales, getTranslations, getFieldLabels

Service Name: i18n · Criticality: core
Implementations: @objectstack/service-i18n (production — file-based) · In-memory fallback (createMemoryI18n from @objectstack/core)
Route Mount: /api/v1/i18n
Contract: II18nService in @objectstack/spec/contracts

Service Registration

EnvironmentProviderRegistration
ProductionI18nServicePluginFile-based FileI18nAdapter loads JSON locale files from disk
In-memory fallback@objectstack/core (createMemoryI18n)Two paths, same factory: the kernel pre-injects it for any unfilled core slot (CORE_FALLBACK_FACTORIES), and AppPlugin registers it during start when the stack declares translation bundles and no i18n service is present. Self-describes as degraded (ADR-0076 D12)
DevelopmentDevPluginAuto-wires I18nServicePlugin when the stack declares translations; otherwise the AppPlugin fallback above applies. DevPlugin registers no stub of its own (ADR-0115)
// Production (real service)
kernel.use(new I18nServicePlugin({ defaultLocale: 'en', localesDir: './i18n' }));

// Development (automatic — DevPlugin wires I18nServicePlugin when translations are declared)
kernel.use(new DevPlugin());

Discovery & Handler Consistency

The Discovery API (/api/v1 or /.well-known/objectstack) and the i18n route handler (/api/v1/i18n/*) both use the same async resolution chain to detect i18n availability:

getServiceAsync() → getService() → context.getService() → services Map

This ensures that discovery.services.i18n.status always matches the actual runtime behavior — a service registered via any mechanism (sync Map, async factory, or context) will be reported correctly in both places.

The locale field in the discovery response is populated from the actual i18n service:

  • locale.default — from i18nService.getDefaultLocale() (falls back to 'en')
  • locale.supported — from i18nService.getLocales() (falls back to [default])

AppPlugin Auto-Loading

When an app bundle includes an i18n config and translations array, AppPlugin automatically loads the translation data into the i18n service during the start phase:

export default defineStack({
  manifest: { id: 'com.example.crm', namespace: 'crm' },
  i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'] },
  translations: [CrmTranslations],   // TranslationBundle[]
});

AppPlugin will:

  1. Set the default locale via i18nService.setDefaultLocale()
  2. Call i18nService.loadTranslations(locale, data) for each locale in every bundle
  3. Skip gracefully if no i18n service is registered (no errors, just a debug log)

REST API Endpoints

MethodPathDescription
GET/api/v1/i18n/localesList available locales
GET/api/v1/i18n/translations/:localeGet all translations for a locale
GET/api/v1/i18n/labels/:object/:localeGet translated field labels for an object

11–15. Infrastructure Services

cache, queue, and job are core services: like i18n, the kernel auto-injects an in-memory fallback when no plugin registers them (see CORE_FALLBACK_FACTORIES in packages/core/src/fallbacks/). The optional services (file-storage, search) stay disabled until a plugin provides them.

ServiceDescription
file-storageUnified upload/download/delete via @objectstack/service-storage, which mounts /api/v1/storage itself. Adapters: local FS and S3 (the S3 adapter's endpoint + path-style options cover S3-compatible services such as MinIO and R2).
searchNothing ships. ISearchService and the engine enum (elasticsearch, meilisearch, …) exist in @objectstack/spec, but no package implements the contract or registers the search slot, so CORE_SERVICE_PROVIDER.search is null.
cacheGeneral-purpose cache. In-memory fallback; memory or Redis adapter via @objectstack/service-cache.
queueMessage queue. In-memory fallback; durable DB-backed adapter (sys_job_queue) via @objectstack/service-queue (no BullMQ/Redis adapter is shipped).
jobScheduled task execution via @objectstack/service-job. In-memory fallback; interval, cron, and DB-backed adapters with concurrency policy.

Roadmap Status

The three-phase build-out this page used to propose has landed, under package names that are service-* more often than plugin-*. Use the real names — a remedy naming a package that cannot be installed is a dead end, which is why CORE_SERVICE_PROVIDER is CI-checked against the workspace.

Shipped

CapabilityPackage
Identity engine — sessions, JWT, user CRUD, OAuth2/OIDC, SCIM@objectstack/plugin-auth
RBAC, field-level permissions, RLS@objectstack/plugin-security
Record sharing + share links@objectstack/plugin-sharing
Metadata persistence (sys_metadata), HMR, cluster cache invalidation@objectstack/metadata · @objectstack/service-cluster
Cache — memory + Redis adapters@objectstack/service-cache
Flow orchestration, triggers, approval-node pauses@objectstack/service-automation
Internationalization@objectstack/service-i18n
File storage — local FS + S3@objectstack/service-storage
DB drivers — PostgreSQL / MySQL / SQLite (Knex), MongoDB, SQLite-WASM, in-memory@objectstack/driver-sql · driver-mongodb · driver-sqlite-wasm · driver-memory
Notification engine + inbox@objectstack/service-messaging
Scheduled tasks — interval, cron, DB-backed@objectstack/service-job
Message queue — memory + durable DB-backed@objectstack/service-queue
Realtime pub/sub (in-process; no HTTP/WS surface yet)@objectstack/service-realtime

Still open

SlotState
uiNothing registers the slot. ViewProtocol's five declared-and-unrouted methods were retired in v17 (#6239); view CRUD runs through /api/v1/meta, and /api/v1/ui/view/:object is served by the protocol service.
searchNothing ships. Contract and engine enum exist in @objectstack/spec only.
aiNothing in this repo — service-ai (chat, completion, models, conversations) is Cloud/EE.
realtime transportThe service exists but no WebSocket/SSE route is mounted, so routes.realtime is deliberately never advertised.

The workflow slot used to sit in this table ("nothing ships, no consumer"). It was retired outright in v17 (#4451, per ADR-0115 Evidence 5): the capability lives in state_machine validation rules, approval flow nodes (ADR-0019) and record_change flows, so there is nothing left for a slot to promise.


Plugin Implementation Pattern

import type { Plugin } from '@objectstack/core';
import type { IDataEngine } from '@objectstack/spec/contracts';

let authService: AuthServiceImpl;

export const authPlugin: Plugin = {
  // Plugin names are reverse-domain (e.g. 'com.objectstack.auth'),
  // not the npm package name.
  name: 'com.example.auth',

  // ADR-0116 — declare what init() needs and what it unconditionally
  // registers, so a misordering is a named boot error instead of a crash.
  requiresServices: ['data'],
  providesServices: ['auth'],

  async init(ctx) {
    // Register the 'auth' CoreServiceName value
    const engine = ctx.getService<IDataEngine>('data');
    authService = new AuthServiceImpl(engine);
    ctx.registerService('auth', authService);
  },

  async start(ctx) {
    // Late-bind anything that must wait for every plugin to be up.
    // (There is no `onReady()` on IAuthService — that is your own type's API.)
    ctx.hook('kernel:ready', async () => {
      await authService.connect();
    });
  },

  // destroy() takes no arguments — capture what you need in module scope
  async destroy() {
    await authService.shutdown();
  },
};

When a plugin registers a service, the discovery endpoint automatically updates:

  • services.auth.enabledtrue, status"available", handlerReadytrue, route"/api/v1/auth" (unless the instance self-declares stub/degraded via __serviceInfo, which is reported verbatim instead)
  • routes.auth"/api/v1/auth" appears in routes
  • features flags follow for the slots that have one — search, files, analytics, ai, workflow, notifications, i18n (websockets is hardcoded false; there is no features.auth)

Next Steps

On this page