REST API endpoint schemas for package lifecycle management.
Base path: /api/v1/packages
Endpoints
POST /api/v1/packages — Install a packageGET /api/v1/packages — List installed packagesGET /api/v1/packages/:packageId — Get package detailsPOST /api/v1/packages/:packageId/rollback — Rollback a packageDELETE /api/v1/packages/:packageId — Uninstall a package
The two READ responses (ListInstalledPackagesResponseSchema,
GetInstalledPackageResponseSchema), the installed-row stages they are bound
to (AssembledInstalledPackageSchema, InstalledPackageAtEitherStageSchema)
and the PackageApiContracts map that names both responses are declared in
./package-api-assembled.zod.ts and published from
@objectstack/spec/api-assembled, not from @objectstack/spec/api.
The reason is weight, not meaning. Four of them carry the ASSEMBLED package
body (the fifth, the route map, names two of those four), which is the whole
metadata vocabulary (../stack.zod) plus the datasource and driver-config
validators behind it. While they sat in this
file, every @objectstack/spec/api bundle linked that tree, and a browser
consumer that imported two string constants from ./sortability.zod paid
for all of it: measured at about twice the gzipped bundle of the same import
before the stage declarations arrived. The maintainer ruling on #18576
(letter B) split the entry so the browser-facing half does not carry them.
⛔ Nothing in this file may import ../stack.zod or anything that reaches
../data/datasource.zod: that edge is exactly what the split removed from
@objectstack/spec/api, and ./api-entry-graph.pin.test.ts refuses it.
A declaration that needs the assembled body goes in the sibling file.
Filter by the installed manifest's type — exact match, unmatched values select nothing
limit
never
optional
[REMOVED] limit / cursor were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on status / type / enabled and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. limit also declared .default(50), so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and hasMore on the response is a constant false that is now true by construction. Filter with status, type and enabled instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key.
cursor
never
optional
[REMOVED] limit / cursor were removed from GET /api/v1/packages in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — both were declared here and read by nothing: the serving door filters on status / type / enabled and then returns every remaining row, so no page was ever withheld and no continuation token was ever minted. limit also declared .default(50), so a reader of the published schema was entitled to believe an unparameterised list is capped at 50 rows; it has never been capped at all, and nothing parses a query string through this schema, so that default has never been stamped onto anything. Delete the key. This route is NOT paginated — it answers the whole installed set, which is a bounded table of tens of rows, and hasMore on the response is a constant false that is now true by construction. Filter with status, type and enabled instead of asking for a window. A first-class package cursor, if one is ever designed, will be a response-minted opaque token, not this key.
Package manifest to install (AUTHORING stage: objects are glob patterns)
settings
Record<string, any>
optional
User-provided settings at install time
enableOnInstall
boolean
optional
Whether to enable immediately after install — honoured at POST /api/v1/packages: true enables the installed row, false disables it, and ABSENT keeps the row's current lifecycle state (a fresh install lands enabled)
overwrite
boolean
optional
Overwrite an already-installed package id instead of answering 409 Conflict
platformVersion
string
optional
Current platform version for compatibility verification
Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — 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.
[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).
Navigation items this package contributes into apps owned by other packages
loading
never
optional
[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine
{ objectstack: string }
optional
Platform compatibility requirements (legacy; superseded by engines)
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); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packaging
Enum<'bundled' | 'manifest-deps'>
optional
Dependency packaging strategy (ADR-0025 §3.3)
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)
Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — 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.
[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).
Navigation items this package contributes into apps owned by other packages
loading
never
optional
[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine
{ objectstack: string }
optional
Platform compatibility requirements (legacy; superseded by engines)
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); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packaging
Enum<'bundled' | 'manifest-deps'>
optional
Dependency packaging strategy (ADR-0025 §3.3)
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)
[REMOVED] manifest.contributes.events was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the list: its only in-repo author already subscribed imperatively in plugin code, so the declaration was decorative. Delete the key. Subscribe to system events in the plugin itself — ctx.hook('kernel:ready', …) (or the events service) from init/start is the enforced channel; record lifecycle hooks register on the data engine.
menus
never
optional
[REMOVED] manifest.contributes.menus was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no renderer ever read it; two alias maps already redirected this spelling to navigation. Delete the key. Declare navigation in the app's navigation tree, or inject items into another package's app via manifest.navigationContributions (ADR-0029 D7), which the engine registers.
themes
never
optional
[REMOVED] manifest.contributes.themes was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: theme registration reaches the registry only through the stack-level themes collection (a ThemeSchema surface, unrelated to this { id, label, path } shape), never through contributes.themes. Delete the key; declare themes in the stack themes collection instead.
translations
never
optional
[REMOVED] manifest.contributes.translations was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no loader ever read these { locale, path } entries; authoring them registered no translations. Delete the key. Declare translations as translation metadata: defineTranslationBundle({ … }) in the stack's translations collection (defineStack({ translations: […] })), which the engine registers and the i18n pipeline serves.
actions
never
optional
[REMOVED] manifest.contributes.actions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it; actions declared here were never invocable. Delete the key. Declare actions in the stack actions collection (registered by the engine) or register imperatively via engine.registerAction.
drivers
never
optional
[REMOVED] manifest.contributes.drivers was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: a storage driver is wired by registering a kernel SERVICE named driver.* (the objectql plugin picks it up and calls registerDriver), and its only in-repo author was registered that way, not by this declaration. Delete the key.
fieldTypes
never
optional
[REMOVED] manifest.contributes.fieldTypes was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — there is no registerFieldType seam anywhere: the declaration advertised an extension point the platform does not have, so authoring it configured nothing. Delete the key. The field-type vocabulary is the spec FieldType enum; extending it is a spec change, not a manifest declaration.
functions
never
optional
[REMOVED] manifest.contributes.functions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it; ObjectQL functions declared here were never registered. Delete the key. Declare functions on the stack (defineStack({ functions: […] })), which the hook binder registers via engine.registerFunction.
routes
never
optional
[REMOVED] manifest.contributes.routes was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the HttpDispatcher never registered a prefix from the declaration, so an entry here parsed cleanly and served nothing while published material kept recommending it. Delete the key. A route that needs real handler CODE is mounted imperatively: resolve the http.server service from the plugin context and register the handler on kernel:ready. A declarative endpoint over a pipeline the platform already runs (query/return records, trigger a flow) is defineStack({ apis }).
commands
never
optional
[REMOVED] manifest.contributes.commands was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the CLI never resolved commands from this declaration: commands are auto-discovered through oclif's native plugin system (the plugin package declares an oclif section in its own package.json; see cli-extension.zod.ts), and the objectstack.config.ts plugins array no longer determines CLI commands. Delete the key.
Applicable locales (BCP-47 tags); omitted applies to every locale. The publish and install paths do not filter by locale — they load every dataset and warn
A navigation contribution: a package injecting nav items into an app it does not own (ADR-0029 D7)
Property
Type
Required
Description
app
string
✅
Target app name to contribute navigation into (e.g. "setup")
group
string
optional
Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a nav_contribution_group_missing diagnostic is emitted — by the runtime at warn, and by os build and os validate at compile time.
priority
integer
optional (default: 200)
Merge priority within the target group — lower applied first (matches object extender priority)
Package manifest to install (AUTHORING stage: objects are glob patterns)
settings
Record<string, any>
optional
User-provided settings at install time
enableOnInstall
boolean
optional
Whether to enable immediately after install — honoured at POST /api/v1/packages: true enables the installed row, false disables it, and ABSENT keeps the row's current lifecycle state (a fresh install lands enabled)
overwrite
boolean
optional
Overwrite an already-installed package id instead of answering 409 Conflict
platformVersion
string
optional
Current platform version for compatibility verification
Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — 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.
[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).
Navigation items this package contributes into apps owned by other packages
loading
never
optional
[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine
{ objectstack: string }
optional
Platform compatibility requirements (legacy; superseded by engines)
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); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packaging
Enum<'bundled' | 'manifest-deps'>
optional
Dependency packaging strategy (ADR-0025 §3.3)
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)
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. Present exactly when the producer opted in at throw time; consumers render it to end users 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)
Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — 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.
[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).
Navigation items this package contributes into apps owned by other packages
loading
never
optional
[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine
{ objectstack: string }
optional
Platform compatibility requirements (legacy; superseded by engines)
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); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packaging
Enum<'bundled' | 'manifest-deps'>
optional
Dependency packaging strategy (ADR-0025 §3.3)
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)
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. Present exactly when the producer opted in at throw time; consumers render it to end users 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)
Required permissions at the AUTHORING stage: legacy string[] or structured plugin block (ADR-0025 §3.2) — 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.
[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).
Navigation items this package contributes into apps owned by other packages
loading
never
optional
[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine
{ objectstack: string }
optional
Platform compatibility requirements (legacy; superseded by engines)
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); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packaging
Enum<'bundled' | 'manifest-deps'>
optional
Dependency packaging strategy (ADR-0025 §3.3)
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)
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. Present exactly when the producer opted in at throw time; consumers render it to end users 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)
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. Present exactly when the producer opted in at throw time; consumers render it to end users 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)
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. Present exactly when the producer opted in at throw time; consumers render it to end users 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)