Package Api Assembled — API Protocol reference
The Package API declarations that carry the ASSEMBLED package body. Published from @objectstack/spec/api-assembled, never from @objectstack/spec/api.
The Package API declarations that carry the ASSEMBLED package body.
Published from @objectstack/spec/api-assembled, never from
@objectstack/spec/api. Everything here is part of the Package API
(/api/v1/packages, ./package-api.zod.ts); what sets these five apart is
that each one embeds the assembled package body, RecordStagePackageBodySchema
from ../stack.zod — or, for the route map, names a schema that does:
AssembledInstalledPackageSchema— the installed row at the assembled stage;InstalledPackageAtEitherStageSchema— the union the read doors serve;ListInstalledPackagesResponseSchema/GetInstalledPackageResponseSchema— the two read responses, bound to that union;PackageApiContracts— the route map, which names both read responses.
Why they have their own entry
The assembled body is the WHOLE metadata vocabulary: ../stack.zod reaches
every collection schema, the datasource declaration and, behind it, the
driver-config validators and the server-only pg URL grammar. Declared inside
@objectstack/spec/api (#17517), that tree became part of every bundle of the
entry — and a browser module that imported two string constants from
./sortability.zod paid for all of it, roughly doubling its gzipped bundle,
because the entry links every chunk its module graph touches and little of
that tree can be dropped by a consumer's tree-shaking. The maintainer ruling on #18576
(letter B) removed the cost rather than watching it: the browser-facing
./api no longer carries these declarations, and this entry does.
⛔ Their MEANING did not change with the move — same schemas, same refusals,
same JSON Schema ids (api/..., still published under json-schema/api/,
because they are API-protocol declarations; only the import path moved).
⛔ Only a declaration that genuinely needs the assembled body belongs here.
Everything else in the Package API stays in ./package-api.zod.ts, which
@objectstack/spec/api publishes; ./api-entry-graph.pin.test.ts pins that
./api reaches neither ../stack.zod nor the datasource declaration.
Source: packages/spec/src/api/package-api-assembled.zod.ts
TypeScript Usage
import { AssembledInstalledPackageSchema, GetInstalledPackageResponseSchema, InstalledPackageAtEitherStageSchema, ListInstalledPackagesResponseSchema } from '@objectstack/spec/api-assembled';
import type { AssembledInstalledPackage, GetInstalledPackageResponse, InstalledPackageAtEitherStage, ListInstalledPackagesResponse } from '@objectstack/spec/api-assembled';
// Validate data
const result = AssembledInstalledPackageSchema.parse(data);AssembledInstalledPackage
Installed package row whose manifest is the assembled package body
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| manifest | { id: string; namespace?: string; defaultDatasource?: string; version: string; … } | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it |
| source | Enum<'code' | 'marketplace' | 'authored'> | ✅ | Where the installed package came from: code (registered by the deployment's own code), marketplace (installed from the marketplace), or authored (a database-authored package) |
| status | Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'> | optional (default: "installed") | Package state: installed, disabled, installing, upgrading, uninstalling, or error |
| enabled | boolean | optional (default: true) | Whether the package is currently enabled |
| installedAt | string | optional | Installation timestamp |
| updatedAt | string | optional | Last update timestamp |
| installedVersion | string | optional | Currently installed version for quick access |
| previousVersion | string | optional | Version before the last upgrade |
| statusChangedAt | string | optional | Status change timestamp |
| errorMessage | string | optional | Error message when status is error |
| settings | Record<string, any> | optional | User-provided configuration settings |
| upgradeHistory | { fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[] | optional | Version upgrade history |
| registeredNamespaces | string[] | optional | Namespace prefixes registered by this package |
Nested Shape: AssembledInstalledPackage.manifest
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) |
| namespace | string | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") |
| defaultDatasource | string | optional (default: "default") | Default datasource for all objects in this package |
| version | string | ✅ | Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101) |
| type | Enum<'plugin' | 'ui' | 'driver' | 'server' | 'app' | 'theme' | 'agent' | 'objectql' | …> | ✅ | Type of package |
| scope | Enum<'cloud' | 'system' | 'project'> | optional (default: "project") | Deployment scope: cloud | system | project |
| installModes | Enum<'managed' | 'unmanaged'>[] | optional | Install modes this package permits (ADR-0131 D6): managed | unmanaged. Absent means ['managed']. |
| name | string | ✅ | Human-readable package name |
| description | string | optional | Package description |
| permissions | { name: string; label?: string; description?: string; packageId?: string; … }[] | optional | Permission Sets — the ADR-0090 collection half of permissions; at the manifest/authoring stage the same key is the ADR-0025 capability grant instead (ManifestSchema.permissions) |
| objects | { name: string; label?: string; pluralLabel?: string; description?: string; … }[] | optional | Business Objects definition (owned by this package) |
| datasources | { name: string; label?: string; driver: string; config: Record<string, any>; … }[] | optional | External Data Connections |
| dependencies | Record<string, string> | optional | Package dependencies |
| configuration | never | optional | [REMOVED] manifest.configuration was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, properties.*.secret promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in defineStack({ plugins: [new MyPlugin({ … })] }), which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
| contributes | { kinds?: object[] } | optional | Platform contributions |
| data | { object: string; externalId?: string | string[]; mode?: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; columns?: Record<string, object>; … }[] | optional | Seed Data / Fixtures for bootstrapping |
| capabilities | { name: string; label?: string; description?: string; scope?: Enum<'platform' | 'org'>; … }[] | optional | [ADR-0066 D1] Authorization capabilities this package defines (seeded with package provenance) |
| extensions | never | optional | [REMOVED] manifest.extensions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: contributes.kinds registers metadata kinds, navigationContributions injects navigation into other packages' apps, and code-level extension happens in the plugin itself (init/start). |
| navigationContributions | { app: string; group?: string; priority?: integer; items: (object | … +9 more)[] }[] | optional | Navigation items this package contributes into apps owned by other packages |
| engine | { objectstack: string } | optional | Platform compatibility requirements (legacy; superseded by engines) |
| engines | { platform?: string; protocol?: string } | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes engine) |
| runtime | Enum<'node' | 'sandbox' | 'worker'> | optional | Plugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting node → HTTP 422 + manual review) and at boot for a plugin os plugin install installed: only node is loaded (in process, not isolated); sandbox and worker are refused until ADR-0025 M3 routes them |
| packaging | Enum<'bundled'> | optional | Dependency packaging strategy (ADR-0025 v2 §3.3): bundled, the only one and the default — omit the key or write bundled |
| main | string | optional | Entry module of a code-bearing plugin, relative to the plugin root; os plugin build bundles it and writes dist/index.mjs here in the compiled manifest (ADR-0025 §3.4) |
| integrity | Record<string, string> | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) |
| functions | Record<string, string | { handler?: string; effect?: Enum<'pure' | 'writes'> }> | { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' | 'writes'> }[] | optional | Named handler functions, lowered to the refs a JSON document carries |
| datasourceMapping | { namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[] | optional | Centralized datasource routing rules for packages/namespaces/objects |
| translations | Record<string, { objects?: Record<string, object>; picklists?: Record<string, object>; apps?: Record<string, object>; messages?: Record<string, string>; … }>[] | optional | I18n Translation Bundles |
| objectExtensions | { extend: string; fields?: Record<string, object>; fieldPatches?: Record<string, object>; label?: string; … }[] | optional | Extensions to objects owned by other packages |
| picklists | { name: string; label: string; description?: string; options: object[]; … }[] | optional | Shared option lists that select fields reference by name |
| picklistExtensions | { extend: string; options: object[] }[] | optional | Options added to picklists owned by other packages (additive only) |
| apps | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; icon?: string; … }[] | optional | Applications |
| views | { name?: string; label?: string | Record<string, string>; object?: string; list?: object; … }[] | optional | List Views |
| viewItems | never | optional | [MACHINE-ASSEMBLED] Non-container view artifacts of a runtime-assembled manifest (standalone ViewItems, flattened overlays) — written by package export and artifact factories, refused in authored stack sources. |
| pages | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; icon?: string; … }[] | optional | Custom Pages |
| dashboards | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; header?: object; … }[] | optional | Dashboards |
| reports | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; type?: Enum<'tabular' | 'summary' | 'matrix' | 'joined'>; … }[] | optional | Analytics Reports |
| datasets | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; object: string; … }[] | optional | Analytics semantic-layer datasets (ADR-0021) |
| actions | { name: string; label: string | Record<string, string>; description?: string | object | string | Record<string, string>; objectName?: string; … }[] | optional | Global and Object Actions. Unique per scope, not per stack: the runtime keys every action by its owning object's name (or 'global' when object-less), a colon, then the action name, and defineStack refuses two declarations that resolve to one key — both here, both on one object's actions, or one in each position, identical twins included (an embedded action is keyed by the object it is written on, not by its own objectName). One global and one object-bound action may share a name; on that object's route the object's own actions take precedence for by-name readers. composeStacks runs the same key rule across its input stacks (counting distinct stacks, not sites) and names both source stacks on a collision. |
| flows | { name: string; label: string; description?: string; successMessage?: string; … }[] | optional | Screen Flows |
| jobs | { name: string; label?: string; description?: string; schedule: object | object | object; … }[] | optional | Background / Scheduled Jobs (run by IJobService on cron/interval/once schedules) |
| emailTemplates | { name: string; label: string; locale?: string; subject: string; … }[] | optional | Email Templates resolved by IEmailService.sendTemplate({ template, locale }) |
| docs | { name: string; label?: string; description?: string; content: string; … }[] | optional | Package documentation — flat Markdown items compiled from src/docs/*.md (ADR-0046) |
| books | { name: string; label?: string; description?: string; slug?: string; … }[] | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
| positions | { name: string; label: string; description?: string; permissionSets?: string[]; … }[] | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |
| sharingRules | { name: string; label?: string; description?: string; object: string; … }[] | optional | Record Sharing Rules |
| queues | { name: string; label: string; description?: string; members?: object[]; … }[] | optional | [ADR-0142 D1] Ownership queues — named sets of people records can sit in until a member claims them |
| assignmentRules | { name: string; label?: string; description?: string; object: string; … }[] | optional | [ADR-0142 D5] Assignment rules — route new ownerless records to a queue or a person, once, on insert |
| apis | { name: string; path: string; method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; summary?: string; … }[] | optional | API Endpoints — declared endpoints are live from protocol 17; each is gated at publish (ADR-0121) |
| webhooks | { name: string; label?: string; object?: string; triggers?: Enum<'create' | 'update' | 'delete' | 'bulk_update' | 'bulk_delete'>[]; … }[] | optional | Outbound Webhooks |
| agents | { name: string; label: string; avatar?: string; role: string; … }[] | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents |
| tools | { name: string; label: string; description: string; parameters: Record<string, any>; … }[] | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_<name> tools (ADR-0109) |
| skills | { name: string; label: string; description?: string; surface?: Enum<'ask' | 'build' | 'both'>; … }[] | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) |
| hooks | { name: string; label?: string; object: string | string[]; events: Enum<'beforeFind' | 'afterFind' | 'beforeInsert' | 'afterInsert' | 'beforeUpdate' | …>[]; … }[] | optional | Object Lifecycle Hooks, as a JSON document carries them |
| mappings | { name: string; label?: string; sourceFormat?: Enum<'csv' | 'json' | 'xml' | 'sql'>; targetObject: string; … }[] | optional | Data Import/Export Mappings |
| analyticsCubes | { name: string; title?: string; description?: string; sql: string; … }[] | optional | Analytics Semantic Layer Cubes |
| connectors | { name: string; label: string; type: Enum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'>; description?: string; … }[] | optional | External System Connectors. A provider-bound entry (has provider: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via connector_action; credentials are auth.credentialRef references, never inline secrets. An entry with no provider is a catalog descriptor only (NOT dispatchable) — set enabled: false on deliberate descriptors. Unknown provider / name conflict ⇒ hard boot error; a credentialRef nothing resolves, or one the upstream refuses, degrades that instance until the credential is set (ADR-0097). |
| requires | string[] | optional | Capability names this stack requires from the platform (canonical kebab-case tokens from PLATFORM_CAPABILITY_TOKENS; an unknown token is a defineStack error, declared-but-missing ⇒ fail-fast at startup) |
| tiers | string[] | optional | Plugin tier presets to enable; overrides --preset |
Nested Shape: AssembledInstalledPackage.upgradeHistory[number]
| Property | Type | Required | Description |
|---|---|---|---|
| fromVersion | string | ✅ | Version before upgrade |
| toVersion | string | ✅ | Version after upgrade |
| upgradedAt | string | ✅ | Upgrade timestamp |
| status | Enum<'success' | 'failed' | 'rolled_back'> | ✅ | Upgrade outcome |
| migrationLog | string[] | optional | Migration step logs |
GetInstalledPackageResponse
Get installed package response
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| success | boolean | ✅ | Operation success status |
| error | { code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … } | optional | Error details if success is false |
| meta | { timestamp: string; duration?: integer; requestId?: string; traceId?: string } | optional | Response metadata |
| data | { manifest: object; source: Enum<'code' | 'marketplace' | 'authored'>; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; … } | … +1 more | ✅ | Installed package details |
Nested Shape: GetInstalledPackageResponse.error
| Property | Type | Required | Description |
|---|---|---|---|
| code | Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …> | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
| declaredCode | string | optional | The producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
| message | string | ✅ | Readable error message |
| userMessage | string | optional | Producer-marked user-facing refusal text, verbatim, in the request's locale: platform-composed text falls back to the workspace default locale, then en; an app-authored refusal falls back to its declared wording. Present exactly when the producer opted in at throw time; consumers render it to end users as received and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message. |
| refusal | true | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value. |
| category | string | optional | Error category (e.g. validation, authorization) |
| httpStatus | integer | optional | HTTP status of the response carrying this error |
| details | any | optional | Additional error context (e.g. field validation errors) |
| requestId | string | optional | Request ID for tracking |
Nested Shape: GetInstalledPackageResponse.meta
| Property | Type | Required | Description |
|---|---|---|---|
| timestamp | string | ✅ | |
| duration | integer | optional | Server-side processing duration in milliseconds |
| requestId | string | optional | |
| traceId | string | optional |
Nested Shape: GetInstalledPackageResponse.data[option 1]
| Property | Type | Required | Description |
|---|---|---|---|
| manifest | { id: string; namespace?: string; defaultDatasource?: string; version: string; … } | ✅ | Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema |
| source | Enum<'code' | 'marketplace' | 'authored'> | ✅ | Where the installed package came from: code (registered by the deployment's own code), marketplace (installed from the marketplace), or authored (a database-authored package) |
| status | Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'> | optional (default: "installed") | Package state: installed, disabled, installing, upgrading, uninstalling, or error |
| enabled | boolean | optional (default: true) | Whether the package is currently enabled |
| installedAt | string | optional | Installation timestamp |
| updatedAt | string | optional | Last update timestamp |
| installedVersion | string | optional | Currently installed version for quick access |
| previousVersion | string | optional | Version before the last upgrade |
| statusChangedAt | string | optional | Status change timestamp |
| errorMessage | string | optional | Error message when status is error |
| settings | Record<string, any> | optional | User-provided configuration settings |
| upgradeHistory | { fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[] | optional | Version upgrade history |
| registeredNamespaces | string[] | optional | Namespace prefixes registered by this package |
| writable | boolean | ✅ | Whether this server lets an org author into and manage this package (a writable base); false for a read-only package. Computed per read by the server's own predicate, never stored |
| disableable | boolean | ✅ | Whether this server lets an org disable this package: the disable door's own verdict, true for a writable base and for a module of an installed app, false for every other read-only package. A client offers Disable / Enable from it, never from writable. Computed per read by the server's own predicate, never stored |
Nested Shape: GetInstalledPackageResponse.data[option 2]
| Property | Type | Required | Description |
|---|---|---|---|
| manifest | { id: string; namespace?: string; defaultDatasource?: string; version: string; … } | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it |
| source | Enum<'code' | 'marketplace' | 'authored'> | ✅ | Where the installed package came from: code (registered by the deployment's own code), marketplace (installed from the marketplace), or authored (a database-authored package) |
| status | Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'> | optional (default: "installed") | Package state: installed, disabled, installing, upgrading, uninstalling, or error |
| enabled | boolean | optional (default: true) | Whether the package is currently enabled |
| installedAt | string | optional | Installation timestamp |
| updatedAt | string | optional | Last update timestamp |
| installedVersion | string | optional | Currently installed version for quick access |
| previousVersion | string | optional | Version before the last upgrade |
| statusChangedAt | string | optional | Status change timestamp |
| errorMessage | string | optional | Error message when status is error |
| settings | Record<string, any> | optional | User-provided configuration settings |
| upgradeHistory | { fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[] | optional | Version upgrade history |
| registeredNamespaces | string[] | optional | Namespace prefixes registered by this package |
| writable | boolean | ✅ | Whether this server lets an org author into and manage this package (a writable base); false for a read-only package. Computed per read by the server's own predicate, never stored |
| disableable | boolean | ✅ | Whether this server lets an org disable this package: the disable door's own verdict, true for a writable base and for a module of an installed app, false for every other read-only package. A client offers Disable / Enable from it, never from writable. Computed per read by the server's own predicate, never stored |
InstalledPackageAtEitherStage
Installed package row at whichever manifest stage it was installed at
Union Options
This schema accepts one of the following structures:
Option 1
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| manifest | { id: string; namespace?: string; defaultDatasource?: string; version: string; … } | ✅ | Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema |
| source | Enum<'code' | 'marketplace' | 'authored'> | ✅ | Where the installed package came from: code (registered by the deployment's own code), marketplace (installed from the marketplace), or authored (a database-authored package) |
| status | Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'> | optional (default: "installed") | Package state: installed, disabled, installing, upgrading, uninstalling, or error |
| enabled | boolean | optional (default: true) | Whether the package is currently enabled |
| installedAt | string | optional | Installation timestamp |
| updatedAt | string | optional | Last update timestamp |
| installedVersion | string | optional | Currently installed version for quick access |
| previousVersion | string | optional | Version before the last upgrade |
| statusChangedAt | string | optional | Status change timestamp |
| errorMessage | string | optional | Error message when status is error |
| settings | Record<string, any> | optional | User-provided configuration settings |
| upgradeHistory | { fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[] | optional | Version upgrade history |
| registeredNamespaces | string[] | optional | Namespace prefixes registered by this package |
| writable | boolean | ✅ | Whether this server lets an org author into and manage this package (a writable base); false for a read-only package. Computed per read by the server's own predicate, never stored |
| disableable | boolean | ✅ | Whether this server lets an org disable this package: the disable door's own verdict, true for a writable base and for a module of an installed app, false for every other read-only package. A client offers Disable / Enable from it, never from writable. Computed per read by the server's own predicate, never stored |
Nested Shape: InstalledPackageAtEitherStage[option 1].manifest
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) |
| namespace | string | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") |
| defaultDatasource | string | optional (default: "default") | Default datasource for all objects in this package |
| version | string | ✅ | Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101) |
| type | Enum<'plugin' | 'ui' | 'driver' | 'server' | 'app' | 'theme' | 'agent' | 'objectql' | …> | ✅ | Type of package |
| scope | Enum<'cloud' | 'system' | 'project'> | optional (default: "project") | Deployment scope: cloud | system | project |
| installModes | Enum<'managed' | 'unmanaged'>[] | optional | Install modes this package permits (ADR-0131 D6): managed | unmanaged. Absent means ['managed']. |
| name | string | ✅ | Human-readable package name |
| description | string | optional | Package description |
| permissions | { services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] } | optional | Required permissions at the AUTHORING stage: the structured plugin block { services, hooks, network, fs } (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 PermissionSet[] collection instead (AssembledPackageBodySchema) |
| objects | string[] | optional | Glob patterns for ObjectQL schemas files |
| datasources | string[] | optional | Glob patterns for Datasource definitions |
| dependencies | Record<string, string> | optional | Package dependencies |
| configuration | never | optional | [REMOVED] manifest.configuration was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, properties.*.secret promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in defineStack({ plugins: [new MyPlugin({ … })] }), which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
| contributes | { kinds?: object[] } | optional | Platform contributions |
| data | { object: string; externalId?: string | string[]; mode?: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; columns?: Record<string, object>; … }[] | optional | Initial seed data (prefer top-level data field) |
| capabilities | never | optional | [REMOVED] manifest.capabilities was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read implements, provides, requires, extensionPoints or extensions, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level manifest.dependencies, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
| extensions | never | optional | [REMOVED] manifest.extensions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: contributes.kinds registers metadata kinds, navigationContributions injects navigation into other packages' apps, and code-level extension happens in the plugin itself (init/start). |
| navigationContributions | { app: string; group?: string; priority?: integer; items: (object | … +9 more)[] }[] | optional | Navigation items this package contributes into apps owned by other packages |
| engine | { objectstack: string } | optional | Platform compatibility requirements (legacy; superseded by engines) |
| engines | { platform?: string; protocol?: string } | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes engine) |
| runtime | Enum<'node' | 'sandbox' | 'worker'> | optional | Plugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting node → HTTP 422 + manual review) and at boot for a plugin os plugin install installed: only node is loaded (in process, not isolated); sandbox and worker are refused until ADR-0025 M3 routes them |
| packaging | Enum<'bundled'> | optional | Dependency packaging strategy (ADR-0025 v2 §3.3): bundled, the only one and the default — omit the key or write bundled |
| main | string | optional | Entry module of a code-bearing plugin, relative to the plugin root; os plugin build bundles it and writes dist/index.mjs here in the compiled manifest (ADR-0025 §3.4) |
| integrity | Record<string, string> | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) |
Nested Shape: InstalledPackageAtEitherStage[option 1].upgradeHistory[number]
| Property | Type | Required | Description |
|---|---|---|---|
| fromVersion | string | ✅ | Version before upgrade |
| toVersion | string | ✅ | Version after upgrade |
| upgradedAt | string | ✅ | Upgrade timestamp |
| status | Enum<'success' | 'failed' | 'rolled_back'> | ✅ | Upgrade outcome |
| migrationLog | string[] | optional | Migration step logs |
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| manifest | { id: string; namespace?: string; defaultDatasource?: string; version: string; … } | ✅ | The ASSEMBLED package body this row carries, at the stage the registry records it |
| source | Enum<'code' | 'marketplace' | 'authored'> | ✅ | Where the installed package came from: code (registered by the deployment's own code), marketplace (installed from the marketplace), or authored (a database-authored package) |
| status | Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'> | optional (default: "installed") | Package state: installed, disabled, installing, upgrading, uninstalling, or error |
| enabled | boolean | optional (default: true) | Whether the package is currently enabled |
| installedAt | string | optional | Installation timestamp |
| updatedAt | string | optional | Last update timestamp |
| installedVersion | string | optional | Currently installed version for quick access |
| previousVersion | string | optional | Version before the last upgrade |
| statusChangedAt | string | optional | Status change timestamp |
| errorMessage | string | optional | Error message when status is error |
| settings | Record<string, any> | optional | User-provided configuration settings |
| upgradeHistory | { fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[] | optional | Version upgrade history |
| registeredNamespaces | string[] | optional | Namespace prefixes registered by this package |
| writable | boolean | ✅ | Whether this server lets an org author into and manage this package (a writable base); false for a read-only package. Computed per read by the server's own predicate, never stored |
| disableable | boolean | ✅ | Whether this server lets an org disable this package: the disable door's own verdict, true for a writable base and for a module of an installed app, false for every other read-only package. A client offers Disable / Enable from it, never from writable. Computed per read by the server's own predicate, never stored |
Nested Shape: InstalledPackageAtEitherStage[option 2].manifest
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) |
| namespace | string | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") |
| defaultDatasource | string | optional (default: "default") | Default datasource for all objects in this package |
| version | string | ✅ | Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101) |
| type | Enum<'plugin' | 'ui' | 'driver' | 'server' | 'app' | 'theme' | 'agent' | 'objectql' | …> | ✅ | Type of package |
| scope | Enum<'cloud' | 'system' | 'project'> | optional (default: "project") | Deployment scope: cloud | system | project |
| installModes | Enum<'managed' | 'unmanaged'>[] | optional | Install modes this package permits (ADR-0131 D6): managed | unmanaged. Absent means ['managed']. |
| name | string | ✅ | Human-readable package name |
| description | string | optional | Package description |
| permissions | { name: string; label?: string; description?: string; packageId?: string; … }[] | optional | Permission Sets — the ADR-0090 collection half of permissions; at the manifest/authoring stage the same key is the ADR-0025 capability grant instead (ManifestSchema.permissions) |
| objects | { name: string; label?: string; pluralLabel?: string; description?: string; … }[] | optional | Business Objects definition (owned by this package) |
| datasources | { name: string; label?: string; driver: string; config: Record<string, any>; … }[] | optional | External Data Connections |
| dependencies | Record<string, string> | optional | Package dependencies |
| configuration | never | optional | [REMOVED] manifest.configuration was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, properties.*.secret promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in defineStack({ plugins: [new MyPlugin({ … })] }), which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
| contributes | { kinds?: object[] } | optional | Platform contributions |
| data | { object: string; externalId?: string | string[]; mode?: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; columns?: Record<string, object>; … }[] | optional | Seed Data / Fixtures for bootstrapping |
| capabilities | { name: string; label?: string; description?: string; scope?: Enum<'platform' | 'org'>; … }[] | optional | [ADR-0066 D1] Authorization capabilities this package defines (seeded with package provenance) |
| extensions | never | optional | [REMOVED] manifest.extensions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: contributes.kinds registers metadata kinds, navigationContributions injects navigation into other packages' apps, and code-level extension happens in the plugin itself (init/start). |
| navigationContributions | { app: string; group?: string; priority?: integer; items: (object | … +9 more)[] }[] | optional | Navigation items this package contributes into apps owned by other packages |
| engine | { objectstack: string } | optional | Platform compatibility requirements (legacy; superseded by engines) |
| engines | { platform?: string; protocol?: string } | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes engine) |
| runtime | Enum<'node' | 'sandbox' | 'worker'> | optional | Plugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting node → HTTP 422 + manual review) and at boot for a plugin os plugin install installed: only node is loaded (in process, not isolated); sandbox and worker are refused until ADR-0025 M3 routes them |
| packaging | Enum<'bundled'> | optional | Dependency packaging strategy (ADR-0025 v2 §3.3): bundled, the only one and the default — omit the key or write bundled |
| main | string | optional | Entry module of a code-bearing plugin, relative to the plugin root; os plugin build bundles it and writes dist/index.mjs here in the compiled manifest (ADR-0025 §3.4) |
| integrity | Record<string, string> | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) |
| functions | Record<string, string | { handler?: string; effect?: Enum<'pure' | 'writes'> }> | { name: string; handler?: string; packageId?: string; effect?: Enum<'pure' | 'writes'> }[] | optional | Named handler functions, lowered to the refs a JSON document carries |
| datasourceMapping | { namespace?: string; package?: string; objectPattern?: string; default?: boolean; … }[] | optional | Centralized datasource routing rules for packages/namespaces/objects |
| translations | Record<string, { objects?: Record<string, object>; picklists?: Record<string, object>; apps?: Record<string, object>; messages?: Record<string, string>; … }>[] | optional | I18n Translation Bundles |
| objectExtensions | { extend: string; fields?: Record<string, object>; fieldPatches?: Record<string, object>; label?: string; … }[] | optional | Extensions to objects owned by other packages |
| picklists | { name: string; label: string; description?: string; options: object[]; … }[] | optional | Shared option lists that select fields reference by name |
| picklistExtensions | { extend: string; options: object[] }[] | optional | Options added to picklists owned by other packages (additive only) |
| apps | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; icon?: string; … }[] | optional | Applications |
| views | { name?: string; label?: string | Record<string, string>; object?: string; list?: object; … }[] | optional | List Views |
| viewItems | never | optional | [MACHINE-ASSEMBLED] Non-container view artifacts of a runtime-assembled manifest (standalone ViewItems, flattened overlays) — written by package export and artifact factories, refused in authored stack sources. |
| pages | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; icon?: string; … }[] | optional | Custom Pages |
| dashboards | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; header?: object; … }[] | optional | Dashboards |
| reports | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; type?: Enum<'tabular' | 'summary' | 'matrix' | 'joined'>; … }[] | optional | Analytics Reports |
| datasets | { name: string; label: string | Record<string, string>; description?: string | Record<string, string>; object: string; … }[] | optional | Analytics semantic-layer datasets (ADR-0021) |
| actions | { name: string; label: string | Record<string, string>; description?: string | object | string | Record<string, string>; objectName?: string; … }[] | optional | Global and Object Actions. Unique per scope, not per stack: the runtime keys every action by its owning object's name (or 'global' when object-less), a colon, then the action name, and defineStack refuses two declarations that resolve to one key — both here, both on one object's actions, or one in each position, identical twins included (an embedded action is keyed by the object it is written on, not by its own objectName). One global and one object-bound action may share a name; on that object's route the object's own actions take precedence for by-name readers. composeStacks runs the same key rule across its input stacks (counting distinct stacks, not sites) and names both source stacks on a collision. |
| flows | { name: string; label: string; description?: string; successMessage?: string; … }[] | optional | Screen Flows |
| jobs | { name: string; label?: string; description?: string; schedule: object | object | object; … }[] | optional | Background / Scheduled Jobs (run by IJobService on cron/interval/once schedules) |
| emailTemplates | { name: string; label: string; locale?: string; subject: string; … }[] | optional | Email Templates resolved by IEmailService.sendTemplate({ template, locale }) |
| docs | { name: string; label?: string; description?: string; content: string; … }[] | optional | Package documentation — flat Markdown items compiled from src/docs/*.md (ADR-0046) |
| books | { name: string; label?: string; description?: string; slug?: string; … }[] | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
| positions | { name: string; label: string; description?: string; permissionSets?: string[]; … }[] | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |
| sharingRules | { name: string; label?: string; description?: string; object: string; … }[] | optional | Record Sharing Rules |
| queues | { name: string; label: string; description?: string; members?: object[]; … }[] | optional | [ADR-0142 D1] Ownership queues — named sets of people records can sit in until a member claims them |
| assignmentRules | { name: string; label?: string; description?: string; object: string; … }[] | optional | [ADR-0142 D5] Assignment rules — route new ownerless records to a queue or a person, once, on insert |
| apis | { name: string; path: string; method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; summary?: string; … }[] | optional | API Endpoints — declared endpoints are live from protocol 17; each is gated at publish (ADR-0121) |
| webhooks | { name: string; label?: string; object?: string; triggers?: Enum<'create' | 'update' | 'delete' | 'bulk_update' | 'bulk_delete'>[]; … }[] | optional | Outbound Webhooks |
| agents | { name: string; label: string; avatar?: string; role: string; … }[] | optional | AI Agents — platform-internal (ADR-0063 §2): the kernel ships exactly two (ask/build); third parties extend via skills, not agents |
| tools | { name: string; label: string; description: string; parameters: Record<string, any>; … }[] | optional | AI Tool metadata records — optional refinement layer, never required: the default path is skills referencing platform tools or materialised action_<name> tools (ADR-0109) |
| skills | { name: string; label: string; description?: string; surface?: Enum<'ask' | 'build' | 'both'>; … }[] | optional | AI Skills (reusable capability bundles — the third-party AI extension primitive, ADR-0063) |
| hooks | { name: string; label?: string; object: string | string[]; events: Enum<'beforeFind' | 'afterFind' | 'beforeInsert' | 'afterInsert' | 'beforeUpdate' | …>[]; … }[] | optional | Object Lifecycle Hooks, as a JSON document carries them |
| mappings | { name: string; label?: string; sourceFormat?: Enum<'csv' | 'json' | 'xml' | 'sql'>; targetObject: string; … }[] | optional | Data Import/Export Mappings |
| analyticsCubes | { name: string; title?: string; description?: string; sql: string; … }[] | optional | Analytics Semantic Layer Cubes |
| connectors | { name: string; label: string; type: Enum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'>; description?: string; … }[] | optional | External System Connectors. A provider-bound entry (has provider: openapi/mcp/rest) is materialized into a live, dispatchable connector at boot and referenced by flows via connector_action; credentials are auth.credentialRef references, never inline secrets. An entry with no provider is a catalog descriptor only (NOT dispatchable) — set enabled: false on deliberate descriptors. Unknown provider / name conflict ⇒ hard boot error; a credentialRef nothing resolves, or one the upstream refuses, degrades that instance until the credential is set (ADR-0097). |
| requires | string[] | optional | Capability names this stack requires from the platform (canonical kebab-case tokens from PLATFORM_CAPABILITY_TOKENS; an unknown token is a defineStack error, declared-but-missing ⇒ fail-fast at startup) |
| tiers | string[] | optional | Plugin tier presets to enable; overrides --preset |
Nested Shape: InstalledPackageAtEitherStage[option 2].upgradeHistory[number]
| Property | Type | Required | Description |
|---|---|---|---|
| fromVersion | string | ✅ | Version before upgrade |
| toVersion | string | ✅ | Version after upgrade |
| upgradedAt | string | ✅ | Upgrade timestamp |
| status | Enum<'success' | 'failed' | 'rolled_back'> | ✅ | Upgrade outcome |
| migrationLog | string[] | optional | Migration step logs |
ListInstalledPackagesResponse
List installed packages response
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| success | boolean | ✅ | Operation success status |
| error | { code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … } | optional | Error details if success is false |
| meta | { timestamp: string; duration?: integer; requestId?: string; traceId?: string } | optional | Response metadata |
| data | { packages: (object | object)[]; total?: integer; nextCursor?: string; hasMore: boolean } | ✅ |
Nested Shape: ListInstalledPackagesResponse.error
| Property | Type | Required | Description |
|---|---|---|---|
| code | Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …> | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
| declaredCode | string | optional | The producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
| message | string | ✅ | Readable error message |
| userMessage | string | optional | Producer-marked user-facing refusal text, verbatim, in the request's locale: platform-composed text falls back to the workspace default locale, then en; an app-authored refusal falls back to its declared wording. Present exactly when the producer opted in at throw time; consumers render it to end users as received and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message. |
| refusal | true | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value. |
| category | string | optional | Error category (e.g. validation, authorization) |
| httpStatus | integer | optional | HTTP status of the response carrying this error |
| details | any | optional | Additional error context (e.g. field validation errors) |
| requestId | string | optional | Request ID for tracking |
Nested Shape: ListInstalledPackagesResponse.meta
| Property | Type | Required | Description |
|---|---|---|---|
| timestamp | string | ✅ | |
| duration | integer | optional | Server-side processing duration in milliseconds |
| requestId | string | optional | |
| traceId | string | optional |
Nested Shape: ListInstalledPackagesResponse.data
| Property | Type | Required | Description |
|---|---|---|---|
| packages | ({ manifest: object; source: Enum<'code' | 'marketplace' | 'authored'>; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; … } | … +1 more)[] | ✅ | Installed packages |
| total | integer | optional | Total matching packages |
| nextCursor | string | optional | Cursor for the next page |
| hasMore | boolean | ✅ | Whether more packages are available — this door serves one page, so always false |
Misc
Misc schemas of the ObjectStack API Protocol: ResolvedBook, ResolvedEntry, ResolvedGroup — each property with its type, default and a TypeScript example.
Package Lifecycle
Response payloads for the dispatcher-served packages.* lifecycle routes — the ADR-0067 commit timeline, the ADR-0033 draft batch doors.