18.5.0
Release notes and upgrade checklist for 18.5.0, compiled from the 284 changesets on `main` that its Version Packages pull request consumes. A minor that carries breaking changes: a 10 MiB request-body limit, tighter SSO account linking, connectors and tools held per namespace, a disabled package that holds no name, localized save refusals, and a new os migrate audit-side-copies command.
How this page was compiled. From the ## 18.5.0 sections the Version
Packages pull request #33097 writes into the CHANGELOGs at its head
25559f43, backend and ui/ alike, whose parent is main at b76df9a6.
Its bullets were deduplicated on their full text, after dropping
Updated dependencies bullets, which leaves 283 entries, one per changeset
the pull request consumes. One changeset landed on main after that head, at
1e65a389, and is read from its file the same way, which makes 284. Each entry is placed by classifyReleaseBullet in
scripts/release-github-releases.mjs, the function that groups the GitHub
Release body, so the five sections below use its five headings, in its order,
and its counts: 45 need you to act, 22 carry a registered
os migrate meta entry, 70 add capabilities, 1 declares a semantic
change, and 146 are other fixes. The breaking entries are listed in full,
each with what changes and what to do, read from its changeset; the
CHANGELOG entry under that commit carries the whole before and after. The new
capabilities are listed one line each, the changeset's own summary. The 146
other fixes are counted by area, and the ones that bear on an upgrade are
named. Areas are placed mechanically, from the words of each summary and the
packages the entry touches, for finding your way. Two new-capability
summaries are shortened or put in plain words; the breaking lines and the
自动迁移 rows are written from their changesets. The
upgrade checklist is compiled from the changesets and
has not been walked.
Release status: 18.4.0 is released, and 18.5.0 is not yet. This page was written ahead of the 18.5.0 cut, from the Version Packages pull request that will publish it. Until that pull request merges and the publish is approved, npm's
latesttag reads18.4.0. Changesets that land onmainbefore the cut join the release, and this page is refreshed to match.
Highlights — 18.5.0
- ⚠️ A minor that breaks. 67 of the 284 entries are declared breaking:
24 graded
minorbecause they narrow the metadata protocol, and 43 gradedpatchunder the level rule. 45 need you to act; 22 carry anos migrate metaentry that lists what to check. Read the version number. - The HTTP server holds a request-body limit. A JSON, form or other body
over 10 MiB is answered
413 PAYLOAD_TOO_LARGEand is not read past the limit;maxRequestBodyBytesandOS_HTTP_MAX_BODY_BYTESraise it, and the storage upload doors keep their own limit (67274ac, #33172). A request path holding a backslash is answered400(d4fbb4d, #33225). - Single sign-on links accounts only where the provider has proven the
domain. With SSO domain verification on, a sign-in links to an existing
account, or changes its email address, only at a domain the provider lists
and has verified (
de5eb08, #33307). A provider's domain list takes bare host names (3c42e63, #33267), an SSO provider creates an account only for an email address (3c44a97, #33418), and an Entra ID SAML provider keys its accounts on the object id (480246b, #33490). - Connectors and tools are held per namespace. Two packages of two
namespaces may each declare a connector of the same name, each materialized,
connected and dispatched as its own instance (
322ded7, #33431;199e362, #33218;687e0a5, #33281), and each ship a tool of the same name (c747cf0, #33124). A bare read of a name two packages hold is refused, naming both. - A disabled package holds no name. The
/metareads and lists, the flow and connector doors and the email template send judge only the packages that serve a name (02f55c5, #33130;3269cab, #33405;06b5669, #33646;9951f46, #33361). - Refusals and advisories in the caller's language. A metadata save's
advisories and refusals (
548f8ff, #33276), the duplicate refusal (09da8c7, #33348) and permission refusals (a1e0402, #33290) read in the caller's language, falling back to the workspace default, and the Console shows the server's sentence (d95ddeb, #33576). - Authoring.
i18n.sourceLocalenames the language inline metadata text is written in (0be91f0, #33357); a hook written as a TypeScripthandlercan declarerefusals, and a refusal code can hold several sentences (53f5a4c, #33641); acontrolled_by_parentdetail can ask read access on its master for writes (df9fe94, #33369); and analytics filters on a related record's field (498c967, #33512). - A new
os migratecommand.os migrate audit-side-copiesrewrites the audit copies written before the audit writer stopped copying another record's field values (4e1d327, #33607).
Release facts, as measured
Read on 2026-11-15, before the cut. The cut can add changesets; the refresh re-reads every row.
| Measured | |
|---|---|
| Source | Version Packages pull request #33097 at head 25559f43, one commit on main at b76df9a6, and the one changeset main gained after it, at 1e65a389. 335 first-parent commits after 18.4.0's version commit 181ad7d445 |
| Changesets consumed | 284, every changeset in .changeset/ at 1e65a389. The 283 at b76df9a6 are entries in the 122 CHANGELOG sections the pull request writes: 110 ## 18.5.0 sections, and 12 of private packages at their own versions |
| Levels | 39 entries graded minor, 245 patch, none major |
| Declared breaking | 67: 24 minor, 43 patch. 45 under 需手动处理 and 22 under 自动迁移. Every one of the 62 commits since 18.4.0 whose subject carries a ! has its changeset among them |
| Packages | 110 publish at 18.5.0: 75 @objectstack/*, create-objectstack and 34 @object-ui/*, the same set as 18.4.0. @object-ui/app-shell takes 18.4.1 and stays private |
| npm, before the cut | @objectstack/spec latest is 18.4.0; next is the snapshot 0.0.0-next-20261115100430 of main |
| Console | 53 of the 284 entries touch an @object-ui/* package or @objectstack/console. See Console (Studio) |
No tag, npm version, Docker image or GitHub Release for 18.5.0 exists yet. The 18.5.0 sweep (#33761) was still running, and none of its results are on this page.
What's new in 18.5.0
The version number is not the signal
Every package still versions in lockstep. The level of a release says whether the metadata protocol moved, and nothing else:
| Kind of change | Level |
|---|---|
| Added to the metadata protocol: a new authorable key, accepted value or metadata type | minor |
| Narrowed or retired: a key removed or tombstoned, a value refused, a requirement tightened | minor |
| Semantic: the meaning or default of an authorable key changes | minor |
Everything else: REST, drivers, services, the CLI, the Console, feat: and fix!: alike | patch |
So 18.5.0 carries 43 entries graded patch that say BREAKING, beside 24
graded minor. Pin exact versions of both scopes, and read the release notes
for every version you cross.
需手动处理 — breaking changes you must act on
The 45 entries that are breaking and carry no registered ADR-0087 entry. Each line says what now answers differently and what to do, from the changeset under its commit; the CHANGELOG entry gives the full before and after. The upgrade checklist puts the ones an app is most likely to meet in order.
The HTTP server and API clients
3 entries.
- A request body over 10 MiB is answered
413 PAYLOAD_TOO_LARGEon the data, API and auth routes of both HTTP doors, the self-hosted server andcreateHonoApp, and is not read past the limit. To keep larger bodies, raise the limit:maxRequestBodyBytesonHonoServerPluginorcreateHonoApp,HonoHttpServer.setMaxRequestBodyBytes(), orOS_HTTP_MAX_BODY_BYTES. Or send the content as a file through a storage upload door, which keeps its ownmax_upload_mblimit (67274ac, #33172). - A request path holding a backslash is answered
400 Bad Requestby the Node server, before any route runs, whereGET /api/v1/data\accountused to reach/api/v1/data/account. Browsers andfetchalready send/; a client such ascurlsends what you write. Send the path with/. The same upgrade,hono^4.13.16 and@hono/node-server^2.1.6, clears seven public advisories, and a streamed response whose stream fails in its first two reads is now a500instead of a truncated200(d4fbb4d, #33225). POST /api/v1/messaging/in-app-messagesno longer answers200when nobody received the message.recipientscounts only the people a delivery was accepted for; a send that reached nobody is503 SERVICE_UNAVAILABLEwhen every delivery failed. Read a200as "at least one person", and handle the refusal (118d37a, #33347).
Security, identity, sign-in and approvals
12 entries.
- SSO account linking needs a proven domain. With SSO domain verification
on, an SSO sign-in links to an existing account, or changes its stored email
address, only at a domain the provider lists and has verified, matched
exactly; a subdomain that is not itself listed is refused
EMAIL_DOMAIN_NOT_ALLOWED. Before people at a subdomain first sign in through the provider, list that subdomain on the provider and verify it (de5eb08, #33307). - An SSO provider's domain list takes bare host names only. Registering or
editing a provider with
https://acme.comoracme.com:443is refused400 VALIDATION_FAILED, naming the entry. Writeacme.com; for a provider already holding such an entry, send the bare names asdomaintoPOST /api/v1/auth/sso/update-providerand verify the domain again (3c42e63, #33267). The boot warns about each stored provider with such an entry (03dc626, #33477). - The domain-verification request uses the provider's registered domain.
POST /api/v1/auth/admin/sso/request-domain-verificationignores adomainin the body; a provider whose domain list names no host is answered400 DOMAIN_VERIFICATION_FAILED. A script reads the answer'sdomainanddnsRecordsinstead (7e2eadd, #33314). - An SSO provider creates an account only for an email address. A SAML or
OIDC sign-in that resolves a person to an opaque NameID or a user name no
longer creates an account; an unmapped SAML sign-in reads the standard
email and name claims before the NameID. Existing accounts still sign in.
For a provider whose users are created this way, map an email attribute
(
3c44a97, #33418). - An Entra ID SAML provider keys its accounts on the object id. Accounts
made before keep their NameID key, and a sign-in now reaches them only
through the account's email. For a provider that is not Entra ID and whose
NameID differs between the sign-in page and the portal, name a stable
attribute in
mapAccountKey(480246b, #33490). - With SSO-only mode on, an IdP-provisioned user is given no local
password.
POST /set-initial-password, the administrator's Set Password action and both reset doors answer409 RESOURCE_CONFLICTand write nothing; a platform administrator may still set their own (30e364e, #33177). - Under direct sign-in with the local path
off, the last identity provider cannot be deleted (409 DELETE_RESTRICTED) unless another way in remains. Register another provider first, or setOS_AUTH_DIRECT_SIGN_IN_LOCAL_PATH=hiddenwith email and password sign-in on (a27d88d, #33623). - The last platform administrator cannot switch
admin_full_accessoff when nobody could switch it back on. To switch it off, first declare a platform owner inOS_PLATFORM_OWNER_EMAIL(d4cfbfb, #33424). - Ban works without the better-auth admin plugin. On a deployment with the
admin plugin off, a row whose
sys_user.bannedis true is now refused a session with403 BANNED_USER. Before upgrading such a deployment, list the banned users, platform administrators first, and unban each one who should keep signing in: turn the admin plugin on, callPOST /admin/unban-user, and turn it off again. The same change lets a managed environment add pending, SSO-only people with their positions, permission sets and manager (cbb876c, #33227). - The Setup › Diagnostics › Audit Log entry follows
view_all_audit_log. A custom permission set that grantsmanage_platform_settingswithoutview_all_audit_logno longer shows the entry; grant it, or open the page by URL. A package entry contributed intogroup_diagnosticswithout its ownrequiredPermissionsis now shown to every member who can open Setup: declarerequiredPermissionson it (8a8c21e, #33349). - An out-of-office delegation with an inverted window, or with the
delegator as their own delegate, is refused
400 VALIDATION_FAILEDonvalid_untilordelegate_id; both shapes were stored and never rerouted an approval (0bd902c, #33284). - An approval request whose flow package was uninstalled is refused
409 RESOURCE_CONFLICTwhen another package ships a flow of the same name, instead of being answered from that other flow. Install the request's package again to act on it (b96dd30, #33297).
Namespaces, packages and metadata reads
17 entries. A name two packages hold is read by its package (?package=, the
namespace address, or /meta/:type/:ns/:name); a bare read of it is refused
409 RESOURCE_CONFLICT, naming each holder.
- A connector name two packages hold is refused at a bare read (
GET /meta/connector/NAME,MetadataManager, the history and rollback doors); an environment-authored flow'sconnector_actionnaming such a connector is refused naming both; a flow whose connector resolves to a package with no live instance is not dispatched; and a connector pull of a name with two instances answers409 connector_name_refused. Address the connector by its package. Ten moved answers are listed in the entry (322ded7, #33431). - A disabled package holds no name at the flow and connector doors. A bare
/automationname, the flow action and endpoint, and the connector address now count only the packages that serve a name. Re-enable the package, or address the serving holder by its namespace (06b5669, #33646). - A disabled package holds no name at the email template send. A send of
a name a disabled package holds resolves over the serving holders, and a
caller whose own holding is the disabled one finds no template. Enable the
package (
9951f46, #33361). - A disabled package's customization pair and a dependent's added locale
are left out of the metadata lists (
GET /meta/email_templateand the execution list) while the package is disabled. Re-enable it to list them; a dependent's addition stays readable at the dependent's own address (3269cab, #33405). - A name a disabled package holds through a stored row is judged over the
serving holders: two enabled packages' rows of it answer
409naming both, where save order decided, and an address naming a disabled customization pair answers404. Read each by its package (02f55c5, #33130). - Two packages' declared overrides of one flow or action name each serve
in their own package's slot, and the env-wide flow and action lists no
longer carry an entry stamped with one package that holds the other's
override. Read each override from its own entry, or by
?package=(8403725, #33126). - The history and rollback doors refuse a bare two-holder name.
GET /meta/TYPE/NAME/historyandPOST /meta/TYPE/NAME/rollbackof a name two holders hold answer409; add?package=PKG, once per holder the refusal names, and roll back to one of that holder's versions (b462444, #33193). - A function name the platform and a package both register is refused at a
bare read, naming both, and a name only a disabled package registers
resolves to nothing. Address it by package (
resolvePackageScopedFunction, or a packaged flow), rename one of the two, or enable the package (5ea8ce5, #33439). - The action activation door answers a two-holder name with
409, naming both, instead of404"has no declaration". Read the409as two holders; the door switches the name once one package holds it (d3ceb1d, #33399). - The action route answers by who holds the name on the routed object. An
action name held by a disabled package on the route, the environment on that
object and one package elsewhere now answers "no declaration" there. Enable
the package, remove its action, or address the environment's action at
/api/v1/actions/_env/OBJECT/NAME(0c2aeb9, #33274). - An action authored in the environment on a packaged object is the
environment's. MCP
run_actionandPOST /actions/NAMESPACE/OBJECT/NAMEwith the object's package namespace answer404for it. Call it by the facelist_actionslists (OBJECT-NAME), its bare name, orPOST /actions/_env/OBJECT/NAME; bind a flow action meant for a package's flow with?package=(a752057, #33071). - An install-local uninstall withdraws the package's action handlers and
body hooks at once. A call naming no package on a key another owner still
holds now runs that owner's handler, and that owner's gates (
403,409 ACTION_DISABLED,503) can answer, as a restart already did. Nothing persisted changes (ea67b26, #33622). unregisterPackagekeeps the stored rows that name the package by the_packageIdstamp alone. Over aDatabaseLoader, the package'sobject,view,appandpageitems among them are now kept and reported. RundetectStampOnlyRows, thenunregisterPackage(P, { force: true })orunregisterPackageWithReportto remove the package's own items. Rows that@objectstack/metadata17.0.0–18.3.0 stamped with the host's package are the environment's: the entry says how to find them (3825f57, #33416).- On a host that wires a
DatabaseLoader, an action is held per(object, name). A package-less save on an object no package binds the name to is the environment's own row, and a read naming a package reaches only that package's own binding: save the row as the package's (_packageId) if it is the package's (fc283d6, #33318;0e9ff94, #33422). - A boot no longer turns a name two packages' rows hold into one body. On
a host that wires a
DatabaseLoader, a bare read of such a name after the boot answers409naming both. Name the package, or rename one of the two (dd966fc, #33299). - Addressed history and rollback on a name one row slot holds. A direct
DatabaseLoader.registerRollbackcall that addresses a package and restores a version recorded with no attribution, on a name several row slots hold, is refused naming every holder. Save the body into the package's row instead (8c88db6, #33464).
Flows and automation
1 entry.
- A record-triggered flow is not armed while a subflow it calls retries an
inline outbound call. It still registers, warns once naming the call and
the retried node, and
GET /automation/_statusreports itbound: falsewithunboundCause: 'retried_callout_in_subflow'. In the child the warning names, setdurable: trueon thehttpnode, or remove the retry. A durablehttpnode on a host with no messaging outbox is sent once in the background on the save path, not retried: wire the messaging service with reliable delivery to keep retries (7ffbb1a, #33265).
Metadata the build, lint and publish doors judge
3 entries.
- The
object_extensionpublish door folds the written entry among other packages' extensions by priority, so it judges the definitionos buildjudges: a formula cycle closed through a higher-priority package's field is refused, and a tie or an outranked entryos buildaccepts now publishes. Break the cycle, or rename the entry's field (ee627c6, #33702). - MCP
validate_expressiongives the publish gate's verdict at avalidationorformulasite (b22be27, #33134) and at aflow_condition(8835532, #33168), so it answersok: falsefor an expression the gate already refuses. Nothing the gate accepts moves; fix the expression the tool now names.
The data engine, seeds and import
3 entries.
- A seed load for one organization writes as that organization. Under a
walled tenancy posture, a hook on a seeded row, and every engine read the
write triggers, sees what a person of that organization sees. A hook that
needs every organization's rows uses
ctx.api.sudo()and filters them itself. A reference to a person resolves only to a member of that organization (47a3b95, #33222). - A seed that names a person by an id-shaped value is judged by
membership. If the person is not a member of the load's organization, the
row is seeded with the reference empty and the load's error says so. Add the
person to the organization, or name a member (
b95d915, #33611). - A data import resolves a lookup cell within the row's own master. A row
that names a master and a detail only another master carries is refused
reference_not_found, where it was linked across masters; and a caller who cannot read the target's master field is refusedreference_not_foundfor a scoped cell. Fix the row's master, or grant read on that field. Rows that were refusedreference_ambiguousnow resolve (b070e1c, #33613).
Analytics and datasets
1 entry.
query_datasetrefuses a selection with no measure,400 VALIDATION_FAILED, asPOST /api/v1/analytics/dataset/querydoes, where the SQL driver answered500. Name at least one measure, aslist_datasetslists them (bf02d33, #33141).
Files and storage
2 entries.
- An upload link onto a file field whose
readonlyWhenholds is refused at issue,403 FIELD_NOT_ACCESSIBLE, and no link is written. Mint the link once the record's state unlocks the field, or use the record's attachments (5430b4a, #33079). - A completed chunked upload takes no more chunks (
409 RESOURCE_CONFLICT), and a completion retried after it succeeded answers the same200instead of500(fd7b378, #33249).
TypeScript exports
3 entries. Nothing to do unless your code imports these.
describeManagedApiMethodConflictsis retired from@objectstack/spec/data; word the message fromcheckManagedApiMethodAffordances, which stays (53b3544, #33583).clearSharedDiscoveryCacheis retired from@object-ui/data-objectstack; a test that needs a fresh discovery read gives each case its ownbaseUrl(854d383, #33666).judgesPackageLessRowsreturns a plainboolean, not a type predicate; to narrow astringtoPackageScopedType, callisPackageScopedTypefrom@objectstack/core(4bba8b8, #33483).
自动迁移 — listed for you by os migrate meta
22 breaking entries carry a registered ADR-0087 entry on the protocol-18
step, 23 registry ids between them. All 23 are semantic entries, not
conversions: os migrate meta rewrites nothing for them. It prints each under
"manual change(s) require your judgment", with the surface it applies to, the
replacement and how to tell it is done, so the list below is a checklist the
command repeats. Eleven of them judge your metadata at os validate,
os build, defineStack or a publish door, one refuses a flow step at run
time, nine move what a /meta read or a registry read answers when two
packages hold one name, and one announces a change no door makes yet. os migrate meta --from 17 lists every entry of the
protocol-18 step, the ones earlier 18.x releases added included; the ids in
the last column are this release's.
| Change | What to do | ADR-0087 id |
|---|---|---|
A connector_action step whose input holds a key its action does not declare fails with VALIDATION_ERROR before the handler is called, naming the key and the declared ones; nothing is sent (89defde, #33614) | Drop the key from connectorConfig.input, or pick the action that declares it; for a provider parameter the action does not list, use the connector's pass-through action | connector-action-undeclared-input-key-refused |
An object that shows two visible fields under one label is refused (field-label-duplicate), the platform's injected columns such as owner_id included (3cbf1f2, #33639) | Relabel or hide one of the two; use the injected owner_id for the record owner instead of a second field | object-field-label-duplicate-refused |
A nameField, displayNameField or activityMilestones[].field that names no field of the object is refused at os build, os validate, os lint and the object publish door (6222ecb, #33370) | Point it at a field the object declares | object-field-pointer-unknown-refused |
A formula that reads another formula reads its computed value, and a cycle of formulas is refused where it is authored (6482c08, #33108) | Break the cycle | formula-cycle-refused |
os validate, os build and os lint refuse a dashboard widget in a package of a multi-package artifact whose dataset only a package outside its declared dependency closure holds, or two packages inside it hold; the runtime refused both reads already (40c73a8, #33121) | Declare the dependency on the dataset's package, or bind the widget to one holder's dataset | dashboard-dataset-outside-reader-closure-refused |
The object_extension publish door judges a computed field an extension adds by the build's field-formula pass, against the extended object as served (3e19223, #33119) | Fix the expression the door names | object-extension-door-computed-field-expression-judged |
os build and the object_extension publish door refuse a formula an extension adds that reads its own value, directly or through another formula (0ba30eb, #33442) | Break the cycle | object-extension-formula-cycle-refused |
Each validation rule an object extension adds is judged by the object's own rule pass, against the extended object as served (27d042f, #33205) | Fix the rule the build names: a misspelt field, an unregistered function or an unguarded nullable operand | object-extension-validation-rule-expression-judged |
The one-hop leaf read through a lookup an extension adds, and the regex and JSON-schema checks of each validation rule an extension adds, are judged as an object's own (bc50d11, #33398) | Fix the leaf name or the pattern the build names | object-extension-added-lookup-hop-leaf-judged, object-extension-validation-rule-artifacts-judged |
A record-mode amount an object extension adds is judged against its object as served, so a parent field that holds no ISO 4217 code, does not exist, or is reached through the extension's own new master-detail is refused (06e53a7, #33621) | Point currencyField at a parent code field the object serves | record-currency-follow-extension-judged-as-served |
Strict defineStack judges a stack that carries packages[] per package body, and counts against its manifest id only the top-level apps no body declares (2c98485, #33074) | Move a second app into its own package body | define-stack-packages-residual-second-app-refused |
Strict defineStack judges a packages[] stack's namespace prefixes per package body, and its top level by the same residual rule (5b2ec82, #33122) | Prefix each body's objects with that body's own namespace | define-stack-packages-body-namespace-prefix-refused |
Two packages of two namespaces may each ship a tool of the same name; an in-process get, unregister, rollback or delete of a two-holder tool name that names no package is refused (c747cf0, #33124) | Pass the package: get('tool', NAME, PKG), unregister('tool', NAME, { packageId }) | tool-names-per-namespace |
A name two enabled packages hold only as stored rows, which no package ships, is refused 409 by the plain read and /published, where save order decided (d635599, #33182) | Read each by ?package= | two-row-holders-bare-read-refused |
A stored row bound to a package lives in that package's own registry slot, so a bare read of an action two packages hold only as rows is refused before and after a restart (728be58, #33513) | Read each package's action by ?package= | stored-row-own-package-slot |
When two packages ship one name and one declares its .custom override, each package's address, list entry and registry slot serve that package's own item (26b5994, #33189) | Read each override from its own package's entry | two-shipper-declared-override-own-slot |
A stored row bound to a package that holds no item of its name is no longer laid into the first holder's registry slot when two packages hold the name (16e3486, #33166) | Read the holder's own item by its package | stored-row-of-a-non-holder-under-two-holders |
Enabling a second holder of an overlay name takes back the package-less row the read-side hydrator laid into the first holder's slot while the second was disabled (84c66d3, #33149) | Address the row with no package, or save it as one holder's | package-less-overlay-row-holder-slot-on-reenable |
The exact read (?package=X) no longer serves an undeclared App-pair row that the registry binds to no package (89d7bef, #33148) | Save the row with overrides.package naming the package whose locale it customizes | pair-row-exact-read-no-owner |
The bare read withholds a customization pair's row of a name every holder of which is disabled, answering 404 (9673030, #33383) | Enable a holder, or read the pair's own address (?package=PACKAGE.custom) | disabled-holder-pair-row-bare-read-withheld |
An App pair row declared for a package that still ships the name is never served at a bundle addition's address as that addition's own item (51ceb43, #33251) | Read the row at the package it declares | declared-row-not-moved-to-addition |
Announced, not yet at any door. An action declaring ai.requiresConfirmation: true will be confirmed by a person, elicited in the MCP client or handed off to the app, and a caller's own confirm: true will stop counting on run_action and resume_run. In 18.5.0 every door still runs such an action on confirm: true; what ships is the contract in @objectstack/spec/contracts (AI_ACTION_CONFIRMATION_CAPABILITY, IActionConfirmationService) (1e65a38, #33725) | Keep sending confirm: true until a release says the doors confirm by elicitation. Then advertise elicitation (form mode) at initialize, or relay the pending answer to the person; an action that is safe unattended declares ai.requiresConfirmation: false | action-requires-confirmation-elicited |
新增能力 — new capabilities
The 70 entries that add to the metadata protocol (Metadata-protocol: added) or widen what is accepted (Clause-②: yes) without breaking anything. Each line is the changeset's own summary.
Console and the @object-ui/* packages
16 entries.
- A refused SSO sign-in names a domain or email refusal instead of an expired link (
f530d25, #33525) - A metadata save's advisories and refusals show the server's sentence in the user's language (
d95ddeb, #33576) - A report drill that rebuilds its filter reads the dataset as the report's reader (ADR-0150 D3) (
9499dcc, #33366) - An
access_deniedsign-in return says the account has no access to this environment, with switch account and back to console (8ed2b89, #33436) - A signed-in person whose account link is refused because the emails differ is told why on the page they land on (
a9da547, #33496) - The default app's Set-default star on Setup → Apps says why it is disabled (
12d090d, #33485) - A list row whose click opens nothing no longer looks clickable (
beff1fb, #33544) - A connector action switch resets its inputs, a multi-field save conflict lists each field, the share page lists fields by the record page's rule, the Studio error box wraps (
159dbe3, #33547) - A signed-in person whose account link is refused as already linked to another user, or as unable to link, is told why on the page they land on (
6446935, #33585) - Cards whose click opens nothing no longer look clickable, and a view authored
navigation: { mode: 'none' }opens no record on a Cmd/Ctrl or middle click (bea3328, #33671) - A failed AI chat conversation create gives back every message sent while it waited, not only the last one (
030ce02, #33673) - The AI build chat says what really happened — the plan card follows the build, a message typed during a reply waits instead of being lost, the draft preview of an unpublished object is an empty list, build issues read in plain words, and the change card names objects and fields by label (
ba32e79, #33333) - The fields no declared field group claims render under their own "Other" heading after the declared groups, on the record page, the record drawer and the form arms (#33229) (
de476f1, #33372) - A row action's declared
disabledpredicate greys its inline row button, and every row surface gives itsdisabledReason(d574341, #33354) - A multi-field row conflict lists each field, and a summary column's footer total is formatted as its cells (
f06f064, #33728) - The AI build chat's plan card says what its 「· 补充」 (
· suggested) field mark means (020ce3d, #33538)
Security, identity, sign-in and approvals
11 entries.
- The audit sealer skips only the chain a chain-local fault belongs to;
os servehands its crypto provider to an AuditPlugin the app composed with none (4f0a5f3, #33503) sharing.controlledByParent.writeRequiresMasterReadlets acontrolled_by_parentdetail ask read access on its master, not edit, for every write (ADR-0055 §3 as amended) (df9fe94, #33369)- An SSO provider can sign the person out of the company sign-in too, per provider and off by default (
68cb68e, #33170) - The Register SSO Provider form names the token, authorization and JWKS endpoints (
3c231cd, #33417) - A query of an opted-in saved dataset writes one
dataset_queryrow insys_audit_log(#31797) (2399e39, #33559) - With single sign-on configured, a signed-out visit can go straight to the identity provider and come back to the page it asked for (
4dbcbdd, #33221) organizationDeleteGate, a host check asked by the organization delete admission (e0ce1b5, #33326)- The organization's pending-invitation limit is a host setting,
OS_ORG_INVITATION_LIMIT, that can be turned off (633d8c4, #33208) - The declared-webhook activation door switches one of two holders the namespace address cannot tell apart, and the upgrade report names every withheld holder (
1f8ce6e, #33204) - A host can keep a platform baseline contribution at read, or decline it, through
platformBaselineOverrides(28c9b42, #33444) member_defaultgrants read and create onsys_comment_reaction, so a stock member can react to the comments they can read (1c25b50, #33617)
Flows, connectors, mail and messaging
7 entries.
- A connector connection is unique per organization and instance, the instance being its package and name, and the connections that name no package keep one per organization through a runtime guard (ADR-0150 D4, D8 row R11, #31699) (
0b89b33, #33199) - An activation row a departed package left switches no remaining webhook holder, and the 18.x ownership ceremony writes a two-holder copy's switch as a row naming no package instead of picking one holder (ADR-0150 D8) (
71ade1a, #33253) - A run record says how much of its duration the run spent suspended, so a resumed screen flow no longer reads as an 18-second execution (
605d712, #33511) - A run summary's node row carries the warnings its steps raised, so a notify whose channel was refused no longer reads as a clean success in the trigger response (
8b53cc8, #33438) - The connector connection store, the connect door and the OAuth callback read and write one instance by its package and name (ADR-0150 D4, #31699) (
199e362, #33218) - The automation engine holds a connector instance by its package and name, and a dispatch addresses the one instance its caller's name resolves to (ADR-0150 D4, #31699) (
687e0a5, #33281) - A flow run's step log declares the
warningsanddroppedFieldsit already carried (c312b26, #33368)
Metadata the build, lint and publish doors judge
5 entries.
- A hook written as a TypeScript
handlercan declarerefusals, and a refusal code can hold several declared sentences with parameters (53f5a4c, #33641) - The flow and hook publish doors accept an unguarded ordering comparison over
created_at/updated_at, the two system columns the platform stamps on every stored row, asos builddoes (a674877, #33434) - A flow
update_recordwithmulti: truewhosefieldsset an input of a stored computed field on its target object is warned at authoring (flow-multi-update-moves-stored-input) (df67557, #33336) - Advise a template
defaultargument that reads like a variable path, and anotifynode that sends an email template without a variable the template declaresrequired(103eb7b, #33321) validate_expressiontakes the flow's declaredvariablesat aflow_condition, so a bare name binds as the flow binds it and the shadowed-read warning comes back (9f736f2, #33255)
Namespaces, packages and metadata reads
4 entries.
- A package publish whose runtime registry refused an object answers
published_unregisteredand names the object, instead ofpublished(8f9eb3d, #33528) - A metadata write's advisories and refusals come back in the caller's language (
548f8ff, #33276) - A customization pair's row of a name two namespaces hold is judged against its own base, whichever package registered first — its removal, its commit revert and the boot census (ADR-0150 D4) (
128b657, #33129) - Deleting an object extension that re-declared a stored computed field fills the object's existing rows with the expression it hands back, as the save door does, and says so per field (
61ed314, #33350)
The data engine, formulas, seeds and import
7 entries.
- The security catalog's list keeps every holder of a permission set name the metadata service holds, so a grant naming a package is judged by (package, name) whatever order the stored rows are listed in (ADR-0150 D4) (
a1604ce, #33252) validate_expressionno longer lists the hidden__searchsearch companion ininScope.fields, and a read of it is refused asos validaterefuses it (615c67f, #33451)- A number or boolean comparand on a text- or option-family field compares as its string, the form the write door stores (#29486) (
f229dfd, #33518) - A restore refused
RESTORE_UNIQUE_VALUE_TAKENby the store names an organization-scoped unique field, and the refusal names the field by its label (4ace1fa, #33375) - The authored-action resync judges "artifact-shipped" by each holder's owner key, so an environment script action on an object no package's action of that name owns gets its handler (#32990) (
5e29b7e, #33271) - The duplicate refusal (
409 UNIQUE_VIOLATION) now reads in the writer's language and names the fields of a composite unique index (09da8c7, #33348) - A roll-up of a stored home amount declares
currencyMode: 'dynamic'and is drawn in the organization's home currency (f64c0f9, #33710)
REST, MCP and the HTTP server
3 entries.
describe_objectservesreadonly: truewhere a write never sets the value (28a2f0f, #33150)- A member's agent finds a colleague by name or email over MCP, to fill a person field (
6a36b79, #33180) - MCP
list_actionsmarks a confirmation-gated actionunavailablefor an AI service account (1bed853, #33144)
Analytics and datasets
3 entries.
- The dataset query door judges a holder whose own address a stored row of two holders contends on that holder's own dataset, naming it in the
409instead of leaving it undetermined (ADR-0150 D4) (534e115, #33491) - A dataset over a field named after a SQL keyword answers rows instead of 500 (
24f2f40, #33266) - The engine-aggregate path filters on a related record's field — a one-hop dotted
whereleaf ('order.status') is served as the engine's nested-relation condition instead of refused (#33335) (498c967, #33512)
Files and storage
5 entries.
- An upload link for a file field of a record not yet created, so an agent can create a record whose file field is required (
6718884, #33123) - One upload link takes several files on a multi-file field (
5e250a6, #33151) - Every byte-reading upload door holds a body with no declared length to its own bound, exactly as it holds a declared one (
dd291b8, #33323) - The upload-link page names the record and the field for the link's own user, and shows the expiry in the reader's time zone (
de31809, #33185) - The uploader removes an attachment they uploaded, through an own-row door, with no delete bit on the member grant (
ba35dfd, #33322)
The CLI, deployment and audit
4 entries.
- A deployed runtime tells search engines not to index its API and Console (
e540baf, #33236) i18n.sourceLocalenames the language the inline metadata text is written in, apart from the users'defaultLocale;os lint,os i18n checkandos i18n extractread it as the source language (0be91f0, #33357)os migrate audit-side-copiesruns the at-rest side-copy rewrite on the live audit tables and the archive (4e1d327, #33607)os servehands the data engine its crypto provider atkernel:ready, so a webhook that fires during boot is delivered signed instead of refused for boot order (7f4627c, #33481)
Skills, spec docs and test tooling
5 entries.
- A write the card gives a persona that the build denies it is charged to the build, not the operation (
1e77fef, #33625) - The Build half checks each persona's permission set for what the card has that persona read and write (
3693644, #33630) - The run grader reads no figure out of a record identifier (
INV-003,i3) (3c0033f, #33629) - An irreversible operation the agent applied through its declared confirmation gate, on an explicit instruction, meets
pending_confirmation(temporary) (83cf892, #33706) - A recorded replay re-sends each turn's recorded writes, so an applied operation reads back what the run wrote (
b76df9a, #33764)
语义变化 — semantic changes
The 1 entry that declares Metadata-protocol: semantic and breaks nothing.
- A package that holds a bundle's name only through its own stored row lists the App pair's added member its exact read already serves (ADR-0150 D4) (
1fd5d8a, #33220)
其他修复 — other fixes
The 146 entries that the classifier reads no breaking change and no protocol kind in. They are not listed here: read them in the CHANGELOG of the package they touch, or in that package's GitHub Release under 其他修复. By area:
| Area | Entries |
|---|---|
Console and the @object-ui/* packages | 28 |
| Security, identity, sign-in and approvals | 18 |
| Flows, connectors, mail and messaging | 4 |
| Metadata the build, lint and publish doors judge | 8 |
| Namespaces, packages and metadata reads | 11 |
| The data engine, formulas, seeds and import | 9 |
| REST, MCP and the HTTP server | 8 |
| Analytics and datasets | 2 |
| Files and storage | 2 |
| The CLI, deployment and audit | 2 |
| Skills, spec docs and test tooling | 54 |
Among them, grouped by what they fix:
- Dates stored before 18.4.0 on PostgreSQL. A
datefield that adatetime→datepublish converted on a release before 18.4.0 can hold values one day early on PostgreSQL; the platform does not repair them, and the@objectstack/driver-sqlCHANGELOG entry says how an operator finds and corrects them. On SQLite and MySQL, such a field whose column stayed a date-time column now filters by its calendar day (01d206d, #33270). - Installs, upgrades and package state take effect without a restart. An
upgrade or re-install withdraws the items the new version no longer ships
(
7adb1b5, #33304); the install and enable routes re-read the metadata they bring into service (e3f0cfd, #33448); disabling or uninstalling a package withdraws its flows at once (c43bffd, #33546); and an install-local package keeps its flows after a restart (6455104, #33095). After a restart the package registry serves the manifest written last (9562643, #33295) and the stored install dates (426bb46, #33610). - Access is judged per object and per operation. Each data operation reads
at its own object's access depth when sibling operations share a request
(
86c18df, #33303); operations running at once on one request wait for the row-level membership resolver instead of compiling against an empty set (d01f514, #33474); a declared position rename carries its holders after a hot install too (4a0c480, #33245); and a directory deactivation suspends the AI service accounts the person is accountable for (7c36f52, #33109). - Refusals and advisories in every platform locale. Permission refusals
speak the caller's language (
a1e0402, #33290), and the save door's dashboard, data-model, form-layout, flow, approval, connector, permission-set and security-posture advisories carry a sentence in every platform locale (5c005e0,dd4ce50,6e4a203,a83c366,796e9d4,c09320d). - Boot and readiness.
GET /api/v1/readyreports whether the boot seed has landed, andos serveprints the seed result (218b012, #33337); a boot the organization-ownership guard refuses names a complete apply command (67725f1, #33408); on an upgraded MySQL 8 database the old, narrower connector-connection key retires as on PostgreSQL (c94a4da, #33514). - Files. A file uploaded through an upload link that named no record, and
that no record took, is reclaimed (
a3a3cea, #33209). - Two widenings the classifier files here. Two changesets open with their
Clause-②: yes (widening)line instead of a summary, so the classifier reads no declaration in them: the MCP read tools admit a system object whose author declaredenable.aiReadable: true(3eac63a, #33117), and organization invitation refusals read in the Console's language (5604aeb, #33679). No narrowing opens that way in this release.
Console (Studio)
18.5.0 ships the Console and the @object-ui/* packages from this repository,
at the commit it releases. Every Console change is listed once, in its section
above: 53 of the 284 entries touch an @object-ui/* package or
@objectstack/console, 4 under 需手动处理, 21 under
新增能力 and 28 under 其他修复. What a
Console user notices first:
- Sign-in says why. A refused SSO sign-in names a domain or email refusal
instead of an expired link (
f530d25); anaccess_deniedreturn says the account has no access to this environment (8ed2b89); and a refused account link says why on the page the person lands on (a9da547,6446935). With single sign-on configured, a signed-out visit can go straight to the identity provider and come back to the page it asked for (4dbcbdd). - Saves speak the user's language. A metadata save's advisories and
refusals show the server's sentence in the user's language (
d95ddeb). - Lists and records. A row or card whose click opens nothing no longer
looks clickable (
beff1fb,bea3328); a row action'sdisabledpredicate greys its inline button and gives its reason (d574341); a multi-field save conflict lists each field (159dbe3,f06f064); and fields no field group claims render under their own "Other" heading (de476f1). - The AI chats. The AI build chat's plan card follows the build, and a
message typed during a reply waits instead of being lost (
ba32e79); a failed conversation create gives back every message sent while it waited (030ce02,b654135,baf9f3b). - A lighter first load. The first load no longer downloads the Setup
pages, the package form, the view config panel, the object list, the record
and list views or the Studio designer strings (
2a86e51,68cee82,6603978). - For a host that imports it.
clearSharedDiscoveryCacheleaves the@object-ui/data-objectstackentry (854d383).
Upgrade checklist
⚠️ For an 18.4.0 app. From 18.3.0, walk the 18.4.0 checklist first, and so on back to the 18.0.0 checklist from 17.x.
18.5.0
This list has not been walked. It is compiled from the changesets, ahead of the cut; each step names the entry it comes from.
1. Move the pins
- Move every
@objectstack/*dependency, and every@object-ui/*one, to18.5.0together, with@objectstack/skillsandcreate-objectstackwhere you use them. Pin the exact version: a caret range takes every later 18.x by itself, and a patch can break a non-protocol surface. No package joins or leaves npm in this release.
2. Run the build, and fix what it refuses
os validate
os build
os migrate meta --from 17 # the protocol-18 step, with this release's entriesFix each refusal with its line in 需手动处理 or 自动迁移. The ones an app is most likely to meet:
- an object that shows two visible fields under one label, a field of your
own labelled like the injected
owner_idthe common case (3cbf1f2); - a
nameField,displayNameFieldoractivityMilestones[].fieldnaming no field of the object (6222ecb); - a cycle of formulas, on the object or through an object extension
(
6482c08,0ba30eb), and an extension's computed field, validation rule, lookup leaf or record-mode amount that the object's own passes refuse (3e19223,27d042f,bc50d11,06e53a7); - a
connector_actionwhose input holds a key its action does not declare (89defde), which fails at run time, not at the build; - a
packages[]stack whose second app, or whose body objects' namespace prefixes, strictdefineStacknow judges per body (2c98485,5b2ec82), and a dashboard widget whose dataset sits outside the reader's declared dependencies (40c73a8).
3. Before the first boot
- Raise the request-body limit if your clients send bodies over 10 MiB:
maxRequestBodyBytes, orOS_HTTP_MAX_BODY_BYTES(67274ac). - Without the better-auth admin plugin, unban who should keep signing in.
Ban is enforced there from this release; list the users whose
sys_user.bannedis true, platform administrators first, and unban each one through the admin plugin before upgrading (cbb876c). - With SSO domain verification on, list and verify every subdomain your
people's existing accounts use before they next sign in through the
provider, and rewrite a stored provider's domain entries as bare host names
(
de5eb08,3c42e63). - Declare
OS_PLATFORM_OWNER_EMAILif you may need to switchadmin_full_accessoff on a deployment whose platform administrators all hold it through the Default Organization (d4cfbfb).
4. After the boot
- Run
os migrate audit-side-copies, a dry run first, then--apply. It removes the side-copy columns that audit and activity copies written before this release still carry, on the live tables and the archive. Until it runs, the read-time redaction drops them, so it is cleanup, not a gate (4e1d327). - Update API clients for the doors that answer differently: a bare read of
a connector, tool, function or stored-row name two packages hold answers
409naming each holder (322ded7,c747cf0,5ea8ce5,d635599); the history and rollback doors take?package=(b462444); a path with a backslash is400(d4fbb4d); an in-app message that reached nobody is refused (118d37a); andquery_datasetneeds a measure (bf02d33). - Check seed hooks under a walled posture: a hook on a seeded row reads as
the load's organization, so a hook that needs every organization's rows says
so with
ctx.api.sudo()(47a3b95). - On PostgreSQL, check
datefields converted fromdatetimeon a release before 18.4.0 for values stored one day early, as thedriver-sqlCHANGELOG entry describes (01d206d).
v18
The v18 line: its GA release 18.0.0 and every minor since, how a breaking change reaches you on this line, and one page per release with its own upgrade checklist. The line carries ADR-0131, total organization ownership with no NULL organization_id.
18.4.0
Release notes and upgrade checklist for 18.4.0, compiled from the 1,253 changesets its version commit consumed. A minor that carries breaking changes: names held per namespace with qualified addresses (ADR-0150), a recycle bin (ADR-0148), one-way sync-in from a datasource (ADR-0153), SQL Server and Oracle federation, mobile push, declared field renames, and four new os migrate commands.