18.3.0
Release notes and upgrade checklist for 18.3.0, compiled from the 1,381 changesets its version commit consumed. A minor that carries breaking changes: customization pairs for installed packages (ADR-0146), ownership queues, per-organization connector credentials, one record page that opens in a drawer, a whole-deployment export, and 41 breaking changes that os migrate meta carries.
How this page was compiled. From the ## 18.3.0 sections the version
commit 83d7e4ae33 (#26517) wrote into 110 CHANGELOGs, backend and ui/
alike. Their bullets were deduplicated on their full text, which leaves
1,379 entries, one per changeset (the version commit consumed 1,381; the other
two touch only the private showcase example). 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: 264 need you to act, 41 are
converted for you, 307 add capabilities, 7 declare a semantic change,
and 760 are other fixes. The first four are listed in full, each line the
changeset's own summary with its commit and pull request; the CHANGELOG entry
under that commit carries the before, the after and the fix. The 760 other
fixes are counted by area and left to the CHANGELOGs. The areas are a reading
of each summary, for finding your way, and an entry that spans two areas is
listed once. Four lines say "membership tier" for better-auth's word for
it, as ADR-0090 D3 asks. Each package's Release also carries an
Updated dependencies bullet under 其他修复, which this page does not count.
The
upgrade checklist is compiled from the changesets and
has not been walked.
Highlights — 18.3.0
- An installed package is customized in its pair. Each read-only base
package has one paired
<base>.custompackage, and that pair is the only place an override of a base item (d6e0967, #28554) or a__cextension field (447a2a6, #28724) can be written (ADR-0146). The ensure door creates the pair when it is first needed (0ecc2c4, #28512), Studio customizes a read-only base's view, dashboard, report or flow into it (d62d973, #28711;693e100, #28824), andGET /packages/:id/customizationsanswers what a deployment changed on a base in one read (519089e, #28595). A write that names no package onto a packaged view, dashboard, report, translation or email template is refused (bc02a77, #28850), andos migrate packageless-overlaysmoves the rows Setup saved that way into the pair (63acf35, #28689). - ⚠️ A minor that breaks. 305 of the 1,379 entries are declared breaking:
117 graded
minorbecause they narrow the metadata protocol, and 188 gradedpatchunder the level rule. 264 of them need you to act; 41 are converted byos migrate meta. Read the version number. - Ownership queues and assignment rules. A record can wait in a queue
until a member claims it, and assignment rules route new records
(
a9f654e, #26337, ADR-0142).current_user.queuesis resolved by the platform (e3470ed, #27044). - Connectors carry each organization's credentials.
oauth2andapp-credentialjoin declarative connector-instance auth (3238750, #28213); an administrator connects anauthorization_codeinstance for their organization (e01edb2, #28317), an OAuth-protected MCP server is dispatched with each organization's token (7064615, #28437), andsignedauth arrives with three schemes (b358e79, #28273). An inline secret (672bda0, #28596) and a credential inside a connector URL (1a8ba3f, #29573;cfd237f, #29875) are refused where they are written. - One record page, opened in a drawer. The record page is laid out again:
frameless details, one timeline, related cards the reader arranges
(
eef8373, #27310), and a tab row under the header (bf74607, #28394). A record opened from a list opens in the drawer by default (ea150b7, #29108), andnavigation.modekeepsdrawer,page,new_windowandnone(17b5152, #26804). The page asks for everything it shows in one request (0027c4a, #27713). - A whole deployment exports as installable packages in one read,
GET /packages/exportoros package export(b3d1415, #29755). A disabled package's export carries its items (5afe7d7, #29914), and a connector map the export withholds is named (e756782, #29746). - Amounts in the record's own currency.
currencyMode: 'record'points a currency field at a sibling ISO 4217 code field (4145210, #27268, ADR-0144), and nothing formats in US dollars by default any more (79ed6f7, #27249). - Plugins are bundled, and
os serveloads them.bundledis the one plugin packaging strategy (fe9e856, #27679), andos serveloads the pluginsos plugin installinstalled, re-verifying each first (d4ff1a0, #27374). - Stored values get commands to bring them current.
os migrate blank-single-values(5bd615e, #29615),os migrate option-codes(9b4fa6c, #29700),os migrate text-scalars(2545021, #29776) andos migrate audit-key-material(a5df094, #26772) rewrite what older releases stored. See after the boot.
Release facts, as measured
Read from the version commit 83d7e4ae33, its CHANGELOGs, and npm view on
2026-11-03:
| Measured | |
|---|---|
| Version commit | 83d7e4ae33 (#26517), merged to main at 14:44Z on 2026-11-03 |
| Changesets consumed | 1,381, all deleted from .changeset/ by the version commit. 1,379 are entries in the 110 ## 18.3.0 CHANGELOG sections; the other two (ce9bc10, d3d70d3) touch only @objectstack/example-showcase, which is private and versions as 0.3.25 |
| Levels | 238 entries graded minor, 1,141 patch, none major |
| Declared breaking | 305: 117 minor, 188 patch. 264 under 需手动处理 and 41 under 自动迁移 |
| Packages | 109 publish at 18.3.0: 73 @objectstack/*, create-objectstack and 35 @object-ui/* |
| No longer published | @object-ui/app-shell takes the version but is private (6a7c0fe, #26483), and @object-ui/plugin-ai is retired (c59b1bf, #27042). npm latest for both stays 18.2.0, their last release |
| npm, read during the publish | @objectstack/spec latest read 18.2.0 at 15:12Z and 18.3.0 at 15:21Z (published 15:15:07Z); next reads 18.2.0, older than latest. ⛔ Never install @next for 18 |
| Console | 544 of the 1,379 entries touch an @object-ui/* package. See Console (Studio) |
Three notes for reading the 18.3.0 CHANGELOGs:
- Four entries open with the same sentence.
**Shipped in 18.2.0**, whose CHANGELOG missed this entry; listed here one release late.opens the entries for the navigation group default (21a13f1, #26427), missed scheduled runs (2282aa8, #26485), every cell error of an import row (43714ca, #26454) and the copy button on keyboard focus (b53322d, #26487): the four changesets 18.2.0's notes said were waiting in.changeset/. Only their commits tell them apart, so this page counts entries by their full text, never by their first line. - Four fixes ship in 18.3.0 without an entry in its CHANGELOGs. They
merged after the version pull request was last refreshed and before it
merged, so their code is in the version commit's tree while their
changesets still wait in
.changeset/, and the next release's CHANGELOGs will list them. One is breaking: publish refuses a validation rule (or another fail-closed predicate) that orders a text or boolean field against a number (4c2cd29, #29923), with the ADR-0087 registrationvalidation-type-mismatch-refused. The others: a base package's own export leaves out its.custompair's extension (5f47410, #29930); a session a password reset ends leaves a revocation trail (7b2bb66, #29921); and an organization administrator's refused assignment removal answers the same for a row it cannot read as for a missing one (72d7db0, #29932). Each is in the upgrade checklist where it applies. - Each entry names the commit that added its changeset, and this page cites the same commits. A commit that added several changesets is named by each of them: the 1,379 entries name 1,300 commits.
The 18.3.0 sweep
The last pre-cut QA wave, wave 5, ran at 589049b0, the merge of #29790;
the version commit is 43 first-parent commits after it. Three of its four run
records compare each item with its wave-4 record at 7fa9873f, and the export
check, a new surface, has none:
| Run record | Scope | Verdict |
|---|---|---|
| #29870 | the whole-deployment export and its re-install | 1 targeted check, FAIL on two clauses: a disabled package exported as an empty shell, and a re-export lost an install-local package's objectExtensions |
| #29877 | every priority:P0 item (23), against wave 4's #28927 | 20 PASS, 2 PARTIAL, 1 FAIL. The FAIL moved from PASS: a packaged app's record page lost its field-change history |
| #29890 | the 20 items wave 4 judged FAIL, and two release blockers | 15 PASS, 5 FAIL. Release blocker #29173 passes; the #29015 path fails again |
| #29893 | the studio-authoring area (38 items) | 37 PASS, 1 FAIL, a clause already carried by an open card |
Its p1 cards, all closed before the version commit:
| Card | What it found | Closed by | In 18.3.0 |
|---|---|---|---|
| #29871 | a disabled package exported as an empty shell, so a re-install lost its items | 5afe7d7 (#29914) | yes, with its entry |
| #29878 | a packaged app's record page no longer showed a tracked record's field-change history | a7e984a (#29917) | yes, with its entry |
| #29882 | the v17 refusal os start prints led to a ceremony that blocks, because it did not name the auth secret os start keeps | 790d9cf (#29913) | yes, with its entry |
| #29884 | a dashboard draft the server refuses to read still opened an editable buffer in Studio Interfaces, and a Source edit could autosave over it | 40a35de (#29922) | yes, with its entry |
| #29903 | a read-only base's own export carried its .custom pair's object extension | 5f47410 (#29930) | code only; the entry waits in .changeset/ |
| #29904 | a validation rule comparing a text field with a number published with a warning, then refused every write | 4c2cd29 (#29923) | code only; the entry waits in .changeset/ |
| #29892 | a checklist clause still pinned the wholesale sys_* refusal that rulings replaced | 242665f (#29931), a checklist edit | nothing ships |
| #29811 | the nightly test tiers were red on main, on the showcase smoke | 9edd6e7 (#29889), a test fix | nothing ships |
The Docker image and the GitHub Release bodies were not read for this page.
What's new in 18.3.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.3.0 carries 188 entries graded patch that say BREAKING, beside
117 graded minor. Of all 1,379, 238 are graded minor and 1,141 patch;
none is major. Pin exact versions of both scopes, and read the release
notes for every version you cross.
需手动处理 — breaking changes you must act on
The 264 entries that are breaking and carry no registered ADR-0087
conversion. Each line is the changeset's summary; the CHANGELOG entry under
its commit gives the refusal, the before and after, and the fix. The
upgrade checklist names the ones an app is most likely
to meet.
Console and the @object-ui/* packages
33 entries.
- The calendar and gantt renderers read their binding from the
calendar/ganttblock only; the flat fallback read is retired (for #25923) (d8a9cc5, #27026) @object-ui/app-shellis no longer published to npm (for #25922) (6a7c0fe, #26483)- The record page v3 — frameless details, one timeline, compact related cards the reader arranges, body tables, enlarge in place (
eef8373, #27310) - The record page's related panels lose collapse — the header opens the whole list, ⠿ + ⋯ on hover, ⋯ holds Hide alone, empty panels fold into one line, and the stored arrangement drops
collapsed(ad030d8, #27920) - A tab row closes the record header — 概览, 动态, then each related panel with its count, as many as the row's width fits; an open panel face is the active tab (
bf74607, #28394) - One record page laid out by its own width — details beside a right column from 760 px, one column below; 动态 is the right column's first panel; ⊟ shows or hides the right column (
3d2da4f, #28116) - A decision output declared
multiple: trueis collected as a list and sent as a string array, from the record header and the Approval Center alike (d3175d5, #29372) - A record opened from a list opens in the drawer by default, and the split pane beside the list retires (
ea150b7, #29108) - Studio names an item's state with one word everywhere (
e9e31e9, #29784) - Retire
@object-ui/plugin-aiand its three node types (c59b1bf, #27042) - Retire the zero-consumer
useMentionNotificationshook and theMentionNotificationinterface (3eeb81d, #29732) - Retire the zero-consumer
useCommentSearchandcreatePresenceUpdaterexports (8c8c5b5, #28385) - Retire the unused
ViewSkeletondispatcher andShimmerSkeleton(3bfb357, #27388) - A renderer's props index signature is
unknown, notany, so a forwarded key must be narrowed before it is used (f486e9c, #26293) FormFielddrops its open index signature, and a form-view field'simmutablelocks the control once the record exists (a260556, #28363)- A bare-string predicate is read as CEL, the way the spec defines it and the server reads it (
4c5023a, #27584) evaluatePlainConditionreads a bare-string predicate as CEL, the way the spec defines it andevaluateConditionreads it (5c083e8, #27773)- Take the test-only
resetActionKeyWarningsoff the package entry (8a12a17, #28544) - An object route reads that object's saved views (
GET /meta/view?object=), not every view of every object (for #29550) (1c1a5d4, #29634) - The designer's version-history label leaves every pack (
4d8095b, #29616) - Delete four
publicFormkeys that no screen reads (8a12a17, #28544) - 「动态」 (Activity) names the record page's stream in all ten packs; the timeline column's two keys leave (
3d2da4f, #28116) - The record page's tab row in all ten packs; the enlarged face's Back label leaves (
bf74607, #28394) - The Packaged automation flows keys and Home's Manage Objects keys leave every pack (
794e71c, #29359) - Retire the zero-consumer
useOfflineSync,requestBackgroundSyncandresolveResponsiveValueexports (8c8c5b5, #28385) - Retire
useConfirmDialogandVersionHistoryfrom the entry, and delete the designers' dead collaboration code (389de4a, #29529) - Retire six zero-consumer exports (the collaboration provider and its status indicator, three canvas hooks and the property editor) (
d93acbb, #29388) - Take the test-only
resetUnknownActivityTypeWarningsoff the package entry (8a12a17, #28544) - Retire nineteen zero-consumer exports (the quick-filter bar, the resource-workload grid, the workload math and the shift-band helpers) (
d93acbb, #29388) - Take the test-only
__resetVisibilityPredicateWarningsoff the package entry (8a12a17, #28544) object-formandobject-calendardeclare the four spec keys their renderers read, and@object-ui/types/zodrenames its legacyActionSchematoCRUDActionSchema(for #25923) (607d3bb, #26751)- Retire the zero-consumer
CommentSearchResultinterface (8a12a17, #28544) DashboardComponentSchemarefuses a dashboard that declares two global filters under one key, as the spec does (5651647, #29507)
Packages, overrides and installs
51 entries.
- A same-origin
type: 'api'action is dispatched through the action doors, so its packaged-action switch holds (for #27506) (481ade6, #27649) docs/nav-targetjudges the navigation each package body contributes on a multi-package artifact (ebed73b, #27394)os plugin installand the boot-time loader parse an artifact's compiled manifest throughManifestSchema, so a stale.osplugin(e.g.packaging: 'manifest-deps') is refused with the schema's prescription instead of failing at boot asimport_failed(47c3e12, #27791)- The override write gate — only a base's paired
<base>.custompackage declares an override, and a dependent's item under a base item's name is refused (ADR-0146 D2, #28410) (d6e0967, #28554) __cextension fields only in a base's paired.custompackage, refused at the save, restore and publish doors,os validate, the marketplace install andduplicatePackage(ADR-0146 D10, #28564) (447a2a6, #28724)docs/metadata-embed-refjudges a package's doc against that package and the packages it declares as dependencies (for #27090) (91a4036, #27204)os serveloads the pluginsos plugin installinstalled, re-verifying each before any of its code is imported, and dispatches on the declaredruntimetier;os plugin disable | enable | uninstall(for #27123) (d4ff1a0, #27374)docs/nav-targetjudges the apps a package body carries on a multi-package artifact (6d7634a, #27342)os buildruns the override write gateos validateruns (ADR-0146 D2, #28643) (7dcbc03, #28681)os buildruns the__cextension-field reservationos validateruns (ADR-0146 D10, #28727) (850eab2, #28757)- An ensure door for a base's paired customization package (ADR-0146 D3) (
0ecc2c4, #28512) - An object extension's
fieldPatchesreaches the owner's own fields only, at the registry fold and at the install door alike; the install door refuses a bad patch with 422 (663b7c0, #28791) - A marketplace install-local uninstall the registry would refuse is refused 409 before the ledger write, naming the pair or the extender, with nothing removed (for #28511) (
bca27ad, #28571) - Install-local reads the manifest of a compiled artifact as
os package install <file>sends it, so the protocol handshake and the dependency check run on it (for #28535) (7449e47, #28603) - Install-local judges a ledger entry stored in the CLI's lifted shape through the install door's flatten, at the boot rehydrate and on the sample-data doors (for #28604) (
a06feb7, #28683) - A disabled package's objects are no longer granted by
/auth/me/permissions, and an action that launches into a disabled package is no longer served (d0df873, #28969) - A package built for a NEWER protocol major than the runtime is told to upgrade the runtime, not to run
migrate meta(7dd5a2f, #26530) - The save door refuses a tenant's copy of a packaged validation rule that changes the rule's condition, type or any other key the package owns (for #28448) (
f5e6953, #28478) - The adopt-orphans door refuses to rebind a package-less object carrying a
__cfield into a plain package (ADR-0146 D10, #28735) (c363084, #28768) - The adopt-orphans door asks the save door's owner judges, before the
__cjudge (ADR-0029 D9.9, ADR-0086 D3, #28776) (2a4c003, #28808) GET /api/v1/meta/:type/:name/diffrefuses afromortoversion that has no history row with 404VERSION_NOT_FOUND, as the rollback door already does, instead of answering 200 "no change" or "everything removed" (1c63231, #26540)- A save or a draft publish naming a package that is not installed is refused, for every metadata type; Studio shows "package not found" (
4f93696, #26920) - The per-item publish and a save naming no package no longer extend a row bound to a package that is not installed (
6845035, #27084) - A
searchFieldsoverride naming no field ($searchFields=,$searchFields=,,[]) is refused 400, not read as absent, so a narrowed search no longer runs over the default search fields (for #27484) (86f2afe, #27670) GET /meta/diagnosticsrefuses an unknown type, severity or package instead of answering an all-clear, and keys an alias's stats by its canonical type (for #27514) (875d007, #27620)- An in-process search-field override that names no searchable field is refused, never widened to a default set (for #28120) (
6fd87b7, #28175) - The layered view naming no package refuses a name two packages hold with the package-less writes' own
409 RESOURCE_CONFLICT, instead of serving one holder's stack picked silently (for #28337) (cd065d3, #28583) - A stored row's package in the metadata list is its column's, never its body's (
15e8311, #28671) - Every metadata read states a stored row's package from its column, never from its body (
7e6e8a4, #28734) GET /meta/_draftsrefuses atypethat names no metadata type and apackageIdthat names no package, the way the list and diagnostics doors refuse them, instead of answering an empty draft list (for #29180) (40eab63, #29295)- A package commit's revert plan and its revert address the committing package's own row, and a restore into a
.custompair's row is judged by the override write gate (5f99582, #29797) - Package commit revert and rollback act only on a commit of the package their path names (
f1c1ca5, #29090) - A package's apps and objects never name their own owning package — the owner-stamp refusal covers
objectsandappsat every door (a9782b3, #28745) - A collection item never names its own owning package — package registration refuses a body
_packageIdother than the registering package's (d2c4874, #28587) - The registry fold judges an option patch against the options the object's owner declares, as the install door and
os validatedo (for #28809) (4a49ad7, #28836) - A tenant's reworded copy of a packaged validation rule is one rule with the package's, run once with the tenant's sentence: the registry fold merges validation rules by name (for #27949) (
50015d8, #28445) - A composed stack's top-level picklist that no package body declares and no object field names is refused at boot, naming the list and the stack (for #28159) (
35ffb77, #28231) - Uninstalling a base package is refused while its paired
.custompackage is installed, naming the pair (409, the extender-refusal family) (for #28408) (1e346e9, #28506) - A request with no submitter is recalled, reminded or resubmitted only by the system or the override actor (
2a1be29, #29806) - The package write doors refuse a query parameter they do not read, so
DELETE /packages/:id?keepdata=trueno longer drops the tables the caller asked to keep (e1ab32e, #26919) - The export door refuses an unserved
format, and the clone door refuses a non-objectoverrides, instead of answering as if they were not sent (#27500, #27489) (524befb, #27659) - The clone door refuses a body that carries
overridesbeside other top-level keys, instead of cloning withoverridesalone (for #27669) (dd9921e, #27934) - A bracketed
objectkey on the view list (?object[]=NAME,?object[0]=NAME) and a bracketedpackagekey on the/metalist and book tree (?package[]=ID,?package[0]=ID) are refused 400 naming the key instead of being ignored and answered with every view or every package (08610a9, #29298) - A bracketed
packageIdortypekey onGET /meta/_drafts(?packageId[]=ID,?packageId[0]=ID,?type[]=TYPE,?type[0]=TYPE) is refused 400 naming the key instead of being ignored and answered with every package's or every type's drafts (186a83a, #29440) - The translation bundle a console downloads leaves out Studio's
metadataForms(b49b752, #26159) - The package draft doors refuse a query parameter they do not read, so
publish-drafts?dryRun=trueno longer publishes for real (96e7a43, #26853) POST /packages/:id/publish-draftsrefuses a change note that is not a string with400 VALIDATION_ERRORnamingmessage, instead of publishing with the default note (for #28452) (12333ba, #28527)POST /packages/:id/publish-draftsrefuses a body that is not a JSON object (an array, a string, a number, a boolean) with400 VALIDATION_ERROR, instead of publishing with the default note (for #28541) (d94dd3a, #28593)- A package export refuses when the read of a metadata type the runtime serves fails, naming the package and the type, instead of answering without that type (for #29760) (
dde5df4, #29824) defineStack,os validateandos buildrefuse a collection item whose body_packageIdnames a package other than the one that ships it (e170d42, #28685)QueryTransportParamsSchema.$searchFields,QuerySchema.searchFieldsandFullTextSearchSchema.fieldsdeclare the search-field override the Data API doors accept, instead of any string or list (for #27683) (74aae1e, #27725)
Metadata the build, lint and save doors now refuse
8 entries.
os validateandos buildrefuse a seed record value the seed load would refuse, with the load's own code and sentence, located at the record's field (for #27259) (99aeee6, #27600)- A scaffold into the current directory refuses, writing nothing, when a template file would overwrite one already there (for #29134) (
ad224b7, #29267) - A field group's
visibleWhenis judged like a field's ownvisibleWhen— inos validate/os buildand at the object save door, refused asexpression-invalidat the group's key (ae7f49e, #26928) - An app navigation
visiblethat does not parse is refused like a pagevisibleWhen, and page variables must be unique, named and seeded with a default of their type (256c86b, #28723) - A page variable
sourcenaming no component on the page, and a page predicate reading apage.NAMEthe page does not declare, are refused (d9c4cdc, #28769) - An app navigation
visiblethat is valid CEL but over the parse budget is refused like a pagevisibleWhenof the same size (373081f, #28756) - An object saved back from a translated
/metaread stores its source labels, and an object the registry would refuse is refused at save and at publish, never reported published (for #29673) (589049b, #29790) - The flat
object-calendarandobject-ganttfield-mapping keys leave the published schemas; producers emit thecalendar/ganttblock (for #25923) (b53d9f3, #26411)
Flows, connectors and automation
24 entries.
os validateandos buildrefuse a flow whose structured region the automation engine refuses to register (ADR-0031, #28893) (5d3667e, #29043)os lintrefuses a flow whose structured region the automation engine refuses to register (flow-region-shape-invalid, ADR-0031) (7f368f5, #29495)os validateandos buildrefuse a flow whose top-level graph closes a cycle with notype: 'back'edge, as the automation engine refuses it at registration (dd4a21f, #29595)- The flow clone door is "new similar" — the copy lands switched off in a writable package, by default the source's paired
<base>.custom, never package-less (for #28149) (f23e051, #28621) - A
connector_actioncall whose evaluated input breaks the action's declaredinputSchemafails the step before dispatch, naming each missing or mistyped parameter, and nothing is sent (for #27995) (ec7e15a, #28035) - A
runAs: 'system'flow's record writes keep the reference check while keeping their authorization elevation (for #28881) (9a0c622, #29060) - A flow's
successMessage/errorMessageare{{ }}text slots over the run's variables (b357250, #28720) - The publish-time null-guard gate judges a record-triggered flow's start, edge and decision-branch conditions (
e119e53, #29474) - A flow whose loop, parallel or try_catch region is malformed is refused at publish, and its draft read says so, instead of publishing and then never registering (
0814b89, #29526) - A flow whose loop is not closed by a
type: 'back'edge is refused at publish, and its draft read says so, instead of publishing and then never registering (5f7d554, #29226) - A decision on a request whose flow was deleted while its run was paused is refused 409
RESUME_TARGET_LOSTbefore anything is recorded, instead of being recorded and answered 500RESUME_FAILED(for #26477) (a3009cb, #26544) - A resume of a run parked on a
mapnode, addressed by the parent's run id while the in-flight item's child run is still suspended, is refused with a code naming the child run id, instead of completing the parent past an item that never ran (for #27877) (4793aa4, #27951) - The connector connection door's begin and revoke legs refuse a delegated or agent caller by name; connecting and revoking an organization's connector is a human administrator's act (
5e706e7, #28474) - An
oauth2connector instance withclientSource: 'installer'connects with the OAuth client its administrator entered, and uses it for every exchange and refresh (ec0316e, #29097) - A
mapwhose item run was cancelled records the item as cancelled when the map's own run is resumed (3a2f4c0, #28176) - A connector stored in the environment never reads the host's files (
15e8311, #28671) - A connector pull whose input breaks the read action's declared
inputSchemais refused before the action is called, withVALIDATION_ERRORnaming each parameter (for #28040) (c48cd2c, #28140) - An installed package's connector entry never reads the host's files (
d2c4874, #28587) - A flow whose
save_noticeruns after asubflow/mapwhose child can suspend is registered but not armed (for #28840) (1335c6b, #28875) - The subflow guards see a
subflow/mapcall inside a region body (for #28846) (4748bac, #28959) - A flow whose region body calls a child that can suspend is registered but not armed, and the region refusal cancels the child run it leaves behind (for #28897) (
1ac1c24, #29096) - A flow's record nodes treat a computed filter operand as a value, never as a query operator (
5556455, #29404) defineConnector()refuses a top-level key the connector does not declare, as every other connector door does (50df124, #28815)- A
save_noticenode that a suspending node reaches is refused at the flow parse (for #28806) (2cba2eb, #28838)
Templates, formulas and mail
17 entries.
- The REST email send door requires a send capability and bounds the sender, the custom headers, the organization and identity mail (
5d759b1, #27469) - The override write gate judges an email template by its locale, so a
<base>.customoverride of a locale the base does not ship is refused (for #28746) (5bb7834, #28777) - A template hole takes one formatter, and the
currencyformatter's argument is a literal ISO 4217 code — both refused at compile, never rendered as formatter syntax or in US dollars (dabe771, #26799) - The mail hole grammar refuses a hole whose formatter the whitelist does not name, by name, listing the known formatters (
40320f1, #27217) - The mail hole grammar refuses text after a hole's
|that is not a formatter name, and reads every formatter part exactly as the in-app dialect does (b445807, #27270) - A formula whose
label()names a field that declares no options is refused at author time (86fb645, #28714) - A new gating authoring rule,
email-template-hole-grammar, refuses an email template hole that chains a second formatter or whosecurrencyargument is not a literal ISO 4217 code (dabe771, #26799) email-template-hole-grammarrefuses an email template hole whose formatter the whitelist does not name, naming the hole and listing the known formatters (40320f1, #27217)email-template-hole-grammarrefuses an email template hole whose text after the|is not a formatter name, stating the formatter shape (b445807, #27270)- An email template locale added in a
<base>.custompair is listed and sent on the base's address, and a dependent's added locale is served only under its own package (for #28802) (1920e48, #28828) - A save, publish or rollback naming an installed read-only package itself onto its view, dashboard, report, translation or email template is refused, naming the base's
.custompair (838a802, #29270) - A commit revert or package rollback that would restore a row bound to an installed read-only package onto its view, dashboard, report, translation or email template is refused per item, naming the base's
.custompair (14df37f, #29632) - Create User with no email answers the field-level refusal, so a client can mark the Email input and word it in its own language (for #28289) (
f048e42, #28354) - A completed password reset ends the account's other sessions, and a reset request answers the same way for any address without waiting on its mail (
3a856f6, #29827) - A mail template hole that chains a second formatter, or whose
currencyargument is not a literal ISO 4217 code, fails the render withTEMPLATE_INVALIDinstead of printing formatter syntax or US dollars (dabe771, #26799) - A mail template hole whose formatter the whitelist does not name fails the render with
TEMPLATE_INVALIDinstead of printing the raw value (40320f1, #27217) - A mail template hole whose text after the
|is not a formatter name fails the render withTEMPLATE_INVALIDinstead of being printed as written (b445807, #27270)
The data engine and field values
30 entries.
os validateandos buildrefuse a composed stack's top-level picklist that the boot refuses (for #28248) (3fd4d6b, #28274)- The import dry run previews create rows in the commit's batches, so a batch-aware parent-summary rule refuses in the dry run the rows the commit refuses (for #26684) (
ef7067f, #26778) - An update or upsert import whose match key names a multi-value or structured field is refused with
400 INVALID_REQUESTnaming the field, instead of failing every row on a raw SQL-binding error (for #27465) (f557d90, #27562) - Retire the legacy
defaultexport, which a CommonJSrequire()could not reach as its types described (1c5fe3c, #27323) - A roll-up the engine refuses at every recompute is refused at save and publish, and a child write that landed answers its success (for #28661) (
1617b7f, #28781) - On a federated object whose
owner_idis the platform's injected anchor, the read and bulk-write scopes narrow beloworginstead of going unscoped (for #27425) (f57eddc, #27476) - An
aggregate_recordsaggregation entry carries itsfilterto the engine and refuses an undeclared key by name (for #28550) (20dcc07, #28600) - The engine refuses filtering, sorting, grouping and aggregating on an
internal: truefield for every caller but a system context (b388cfe, #29003) - The MCP data tools refuse to filter, sort, group or aggregate on a field declared
internal: true, with the data door's own 400 INVALID_FIELD, on the stdio transport and on the HTTP bridge's aggregate_records (b48eeb1, #28772) - The Data API refuses an empty projection and a count flag that is neither true nor false, instead of reading them as absent (for #26644) (
4702c46, #26811) - Inside one bulk create, a child rule that reads a parent summary counts the earlier rows of the same batch its rules admitted, so a batch is judged as its rows would be one by one (for #25579) (
af1e1e0, #26680) - A
deleteBehavior: 'restrict'reference refuses the delete of the record it protects even when the referencing row would be cascade-deleted by that same delete (for #26718) (31a6ca7, #26864) - A predicate delete (
multi: truewith a filter) honoursdeleteBehaviorfor every row it matches: arestrictrefuses, amaster_detailcascades, aset_nullclears (for #26871) (b2b3124, #26930) - The in-process engine refuses an empty projection, as every published declaration of a read's projection does (for #27008) (
9fa70b4, #27161) - The in-process engine refuses a projection whose every column is unknown, as it refuses an empty one (for #27165) (
ee9a8d6, #27541) - A non-system write to a
secretfield may only seal a new value or keep its own record's reference (for #27582) (ecd944f, #27695) - A string-typed field refuses a plain object or an array as its value, instead of storing its JSON text (for #29133) (
391216f, #29223) - A number or a boolean written to a text-family field is stored as the string the validator judged (
04e0c2c, #29384) - A select refuses a plain object or an array as its value, and as an element of a multi-value select's array (
e832ddb, #29540) - A nested write's stale-roll-up refusal (
ERR_SUMMARY_RECOMPUTE) never answers for a delete or a transaction that did not land (3291213, #29381) - A hook's own write's stale-roll-up refusal (
ERR_SUMMARY_RECOMPUTE) never answers for the write the hook is bound to, and the unlanded-write refusal leads with the outer write and declares500 INTERNAL_ERROR(f966f01, #29519) - A number or a boolean written to a select, radio, multiselect, checkboxes or tags field is stored as the option code string the validator judged, and any object value is refused (
b391feb, #29638) - A string-typed field refuses a Date, a Map, a Set, a class instance or a typed array as its value, as it already refused a plain object or an array (for #29645) (
85ea224, #29684) - Extend the credential-column guard from aggregates to filter and sort keys (for #27477) (
54c2496, #27477) - An async import job for an unregistered object is refused with 404
OBJECT_NOT_FOUNDbefore any job is created (for #27501) (81d00a4, #27711) - An async import job whose every row failed ends
failed, notsucceeded(for #27676) (d5151a7, #27795) - Both import doors answer for the object as the data door does — the synchronous import refuses an unregistered object 404, and a legacy short name is the object behind it at every door (#27728, #27729) (
fdf844c, #27897) QueryTransportParamsSchema.select/$selectandGetDataRequestSchema.selectdeclare the projection thefindDataandgetDatadoors accept, instead of any string or list (for #26909) (81b4599, #26949)- The engine-side projection declarations (
EngineQueryOptionsSchema.fields, the RPC request'sselect, the legacyDataEngineQueryOptionsSchema.selectand the vector-find request'sfields) declare the projection their transport siblings declare, instead of any list (for #26954) (49d652e, #27007) EngineQueryOptionsSchema.searchFieldsand its aggregate and count siblings refuse an empty list, asFullTextSearchSchema.fieldsdoes (for #28059) (35f2055, #28154)
REST, MCP and query parameters
14 entries.
tools/listfollowsnextCursorthrough every page, and a rejected credential (HTTP 401 / 403) is a fatal configuration fault, not a retried upstream outage (55b5042, #26798)- A declarative
mcpinstance refuses aproviderConfig.includeit cannot read, and an entry naming a tool the server does not list, instead of exposing every tool (4694365, #27985) - An aggregation entry refuses an undeclared key by name on every door, and MCP's
aggregate_recordsreads the same refusal (for #28609) (36fa4ff, #28706) - An
object_extensionitem's refusals fit the item door, and an extension names its target by the key the object is registered under (for #28834) (45efba2, #28853) - The runtime dispatcher's
GET /data/:object/:idrefuses a query parameter other thanselect/expandwith400 VALIDATION_ERROR, asRestServerdoes, instead of dropping it and answering200(67cf0b1, #26947) - The public form submit door refuses a value outside a form row's declared
optionssubset (for #27606) (04141a6, #27766) GET /meta/diagnosticsrefuses an unknown query parameter name instead of ignoring it and answering the full sweep (for #27633) (9cd54fa, #27913)- The cross-object batch door judges each op's API exposure on the object the data door serves for its name (for #27899) (
5f6dc96, #27963) - An empty
?id=on the app list and an empty?object=on the view list are refused 400 naming the parameter instead of being read as absent (f3f7b62, #28602) - A bracketed
idkey on the app list (?id[]=NAME,?id[0]=NAME) is refused 400 naming the key instead of being ignored and answered with the whole list (d659c1d, #29046) - Every filter door refuses a
{ $now, addDays }anchor that carries any other key, asNowAnchorSchemaalready declares, instead of running it on theaddDaysreading alone (c68869c, #26592) - The filter save and validate doors refuse an
addDaysoffset whose{ $field }is a dotted path, in the words SQL push-down already refuses it with on every read (02b1392, #26655) - The
viewwrite door refuses an unknown top-level key on a view item record and on an inline list or form view, as the object, page and dashboard doors do, instead of saving without it (8a218fc, #26851) HttpFindQueryParamsSchemadeclares thecountandselectvalues the list door accepts, instead of a coerced boolean and any string (for #26817) (d887aa9, #26901)
Analytics
17 entries.
- A list view's chart bound to a dataset over another object is refused (
list-view-chart-dataset-foreign-object) (20457f6, #26842) - The view and object publish doors refuse a list-view chart whose dataset, dimension or measure does not resolve (
216a39e, #26906) - A keyed seed whose
externalIdnames no field of its object is refused at save and at publish, and the boot loader warns once for a keyed dataset whose every record keys empty (for #28952) (3cf7f61, #29066) - A
packages[]stack's top-level dataset that no package body declares is registered under the stack's own manifest id, like every other residual item (a81b7cb, #29593) - A date bucket over a formula field is refused alike on every analytics face, instead of a driver error, one null group or a rendered statement (
ac4715a, #29347) POST /analytics/dataset/queryrefuses an unknown key on its request body, and a body naming bothdatasetanddatasetName, instead of answering 200 (for #29156) (fa5e863, #29251)- A dataset refuses a measure
currencythat contradicts its field'sfixedcode, and a derived measure across two differentfixedcodes (ADR-0144 open item 12) (6dc06b2, #27991) - A dataset refuses a derived measure
currencythat contradicts the onefixedcode its operands resolve to (ba4ecf1, #28043) - The dataset draft preview mints and judges a measure the dataset does not declare as the live strategies do, so a misspelled measure is refused
INVALID_FIELDinstead of answered as a row count (8a33dff, #28210) - The dataset draft preview refuses a dimension whose field the object does not have, as the live strategies do, instead of grouping every row under one null key (
4159c70, #28259) - The dataset draft preview refuses a runtimeFilter on a field the object does not have, and a dimension over a JSON column, as the live strategies do, instead of serving an empty or object-keyed answer (
f64724a, #28264) - A dataset dimension spelled through a relationship path the dataset does not declare is refused INVALID_FIELD on the native, ObjectQL and draft-preview faces (
a2e7556, #28553) - Two granularities of one time dimension are refused on every face, not collapsed to the last one (
fa59f78, #28837) - The
/analytics/sqldry run refuses a selection asking one column twice, as/analytics/querydoes, instead of rendering a statement the query refuses (97680bb, #28878) - The draft preview refuses a date bucket over a column that is not a date or datetime, as the published dataset does, instead of serving one null group (
5d7cfe6, #28976) - The /analytics/sql dry run refuses a date bucket over a column that is not a date or datetime, as the query does, instead of rendering it (
0c0d3b6, #29089) - The delegated fallback face refuses a date bucket over a formula or a field that is not a date, as the engine face does (for #29364) (
a922779, #29500)
Security, identity, sharing and approvals
30 entries.
- Every member of an installed bundle is recorded as installed, whatever its entry's
sourcestates (d2c4874, #28587) - An ordinary member of an organization with more than 1,000 members sees every fellow member in
sys_useragain, and an organization too large for the database's bound-parameter limit is refused loudly instead of served a short list (f72bc9e, #29356) - Changing an API key through the data door requires a signed-in session (
04b1402, #27692) searchable-field-unsearchablereports asearchableFieldsentry that names asecretorpasswordfield, which the engine leaves out of every search without a word (755e285, #27811)- A runtime permission-set save refuses a grant on an object that does not exist (
78f78ac, #29093) - A
$searchFieldsentry naming asecret/passwordfield is refused 400INVALID_FIELDat the data door, even when the object'ssearchableFieldsdeclares it, instead of searching the rest of the declared set (for #27829) (6caca51, #27936) - A permission set's name is unique across packages (
710c2da, #27535) - The in-process engine refuses a search-field override that names no field, as the Data API doors and the shared declarations do (for #27732) (
6a648ac, #28108) - The registration door's
_packageIdrefusal calls the shared spec judge (e170d42, #28685) - A share link whose
redactFieldsnames a field the object does not have is refused at mint, instead of being issued with a redaction that hides nothing (3ff32c4, #29293) - Copy Invite Link is no longer offered or answered for a pending invitation whose expiry has passed (
aa46d14, #28529) - An approval decision whose output does not match its declaration is refused before anything is recorded — a single value where
multiple: truedeclares a list, a list where it does not (a bare-key ortextoutput included), a list naming one entry twice, or a picker value that resolves to no user, department, team or held position (d3175d5, #29372) - A membership tier held by more than 10,000 members reaches every holder in sharing and approvals, and a membership read that fails is refused loudly instead of answered with an empty set (
2789ba7, #29497) - Team and position recipients reach every holder past 10,000 rows in sharing and approvals, and a team, position or membership-tier holder read that fails is refused loudly instead of answered with nobody (
9bdfe37, #29602) - Business-unit recipients and approvers reach every member past 10,000 rows, a failed business-unit member read is refused loudly instead of answered with nobody, and a failed
resolveAsexpansion says so (e0187d0, #29647) - A failed
sys_business_unitread refuses instead of answering nobody, and a business unit with more than 1,000 child units keeps every child (4898162, #29703) - A comment's display name is stamped from the caller's session, like its author id (
6d1c659, #29737) - The set-user-manager depth cap counts the whole chain a link creates, including the reports already below the edited user, so a chain linked bottom-up cannot pass
MAX_MANAGER_CHAIN_DEPTH(for #26775) (b29a88c, #26868) invite-memberrefuses a blank membership tier 400ROLE_NOT_FOUND, the way it refuses an unknown one, and writes no invitation (for #29158) (a2fcede, #29296)- A user preference's owner is fixed when the preference is saved (
2ea6042, #29685) - A forced password change now requires a password different from the current one (
6bc7643, #27440) - Security explain refuses a
userIdthat nosys_userrecord has with404 RECORD_NOT_FOUND, naming the id, instead of explaining the baseline principal the resolver builds for any id (6279efe, #26801) security/explainrefuses an explicit emptyuserIdwith 400 instead of explaining the caller (for #27442) (eac6d96, #27714)- A failed business-unit head or standalone manager read refuses instead of answering no head or no manager (
a565019, #29787) - A failed team-graph manager read refuses instead of answering no manager (
bbb98c4, #29836) - Gate webhook redelivery on webhook-administration access (
6cc5374, #27474) GET /api/v1/approvals/requests/:id/actionsanswers404 REQUEST_NOT_FOUNDfor a request that does not exist or that the caller cannot see, as its parent doorGET /api/v1/approvals/requests/:idalready did (a3014cb, #27790)- The permission-set clone door lands "new similar" in a writable target package — by default the source package's
.custom, never package-less (for #28563) (6e11a92, #28636) - Members read through a relationship path answer one refusal on the native, ObjectQL and draft-preview faces (
c01690e, #28686) - A report chart's
seriesBythat cannot split is refused at authoring time — on a single-series chart type, or naming the same dimension asxAxis(16e7a80, #28439)
Datasources
19 entries.
- An install-local install connects and records the datasources the package declares, as a boot of its artifact does (
d70e94a, #28132) - Seeing a datasource's remote structure needs the read capability on every route, writes included (
64646ae, #29812) - A datasource definition's stored row is served only to datasource administrators, whichever exit reads it (for #27884) (
119931d, #28465) - The metadata save doors refuse an object whose
datasourcenames a datasource that does not exist, instead of storing and publishing an object that never registers (for #26724) (c90b5b1, #26912) - A datasource saved through the metadata door may name only the credentials reference its own stored row holds (for #27762) (
642dd4f, #27817) - A datasource restored or published from a stored body may name only the credentials reference its row holds now (for #27818) (
7591c60, #27860) - A plugin driver on an external datasource answers every read correctly or refuses: the engine applies the whole query to the rows the driver returns, up to 1000 of them (for #27277) (
86edcaf, #27403) DatasourceConnectionService.reconnectrequiresprevious, and the engine's datasource read-back answers the stored driver id (for #27727) (07585bf, #27788)POST /api/v1/datasources/:name/testrefuses a name that no saved datasource has with404 RESOURCE_NOT_FOUND, as the other by-name doors do, instead of answering200 { ok: false }(e270dab, #27574)- A plugin-driver datasource connected or reconnected through the connection service keeps the engine's correctness floor (for #27666) (
869fa76, #27715) POST /api/v1/datasources/:name/external/validateanswers a name no saved datasource has404 RESOURCE_NOT_FOUND, asGET /datasources/:nameand the other federation doors do (ca54eac, #27911)- A datasource create may not name a stored credential reference; it stores only the one its own secret is bound under (for #27722) (
8f25cee, #27761) POST /api/v1/datasourcesandPATCH /api/v1/datasources/:namerefuse a top-levelpasswordorconnectionStringby name, with the build door's prescription, instead of dropping it and answering success (f3d84b3, #28607)POST /api/v1/datasources/test,POST /api/v1/datasourcesandPATCH /api/v1/datasources/:namerefuse every top-level keyDatasourceSchemarefuses, with that schema's own message, instead of dropping it (819b101, #28632)- The datasource admin doors store
ssl,autoConnectanddescriptioninstead of dropping them, and the connection test probes the same TLS block a Save stores (dd6349e, #28648) - The datasource admin doors refuse an empty
secretinstead of sealing it over the working credential (0a41075, #29064) - Creating a runtime datasource refuses
drivers, the name the driver-catalog route answers, so a datasource is never created that its edit form cannot load (for #29164) (4771869, #29262) composeStacksrefuses a datasource whoseexternal.connectornames no connector instance the composed inputs declare (for #27803) (6da8c3c, #27939)- The datasource read redactor covers the declared
sslsecret members (f5a400a, #28674)
Files and storage
9 entries.
os plugin publishparses the artifact's compiled manifest throughManifestSchemabefore anything is uploaded, so a stale.osplugin(e.g.packaging: 'manifest-deps') is refused at the publisher's terminal with the schema's prescription instead of failing later at its consumer's install (ed0e71d, #27910)- A row's author or uploader is read-only after creation, like the comment's display name (
6f35b74, #29868) - A file field refuses a file whose upload was never completed, so a record cannot claim a
pendingsys_file(416d9dd, #26531) sys_file's storage-owned columns (key,mime_type,size,status,etag) can no longer be set or changed by a write that does not come from the storage service (a0413fc, #27765)- The chunk upload door refuses a
chunkIndexthe upload did not declare, and the presigned upload door refuses asizethat is not a byte count (85663a5, #27537) - The chunked-upload init door parses its body with
InitiateChunkedUploadRequestSchema, so atotalSizethat is not a positive whole number and achunkSizebelow the declared minimum are refused instead of minting a session that can never complete (ca167f6, #27781) GetPresignedUrlRequestSchema.sizedeclares a whole, non-negative byte count, andfilename/mimeTypedeclare that they are non-empty, the accept set the presigned-upload door enforces (for #27598) (2c683cf, #28136)sys_file's file-reference columns (ref_object,ref_id,ref_field,released_ref_object,released_ref_id) are owned by the file-reference lifecycle and can no longer be set or changed by a write that does not come from it (7e1089e, #28454)InitiateChunkedUploadRequestSchema.filenameand.mimeTypedeclare that they are non-empty, as the chunked-upload init door has always required (for #27801) (a68430a, #27887)
Schemas in @objectstack/spec
12 entries.
- A document that is not OpenAPI 3.x is refused at load — a Swagger 2.0 document included (
55b5042, #26798) current_user.queuesis resolved by the platform from the ownership-queue service, andqueuesjoins the reservedcurrent_userkeys (for #27016) (e3470ed, #27044)- Retirement kits whose conversion retired at or below protocol 16.x leave the tree; the strict unknown-key refusal names the protocol upgrade guide (
dbf7e04, #26599) - A single-word language name (
Chinese,English,Japanese) is refused as a locale (63e3873, #26770) - The user directory no longer returns another user's sign-in phone number to non-admin readers (
ac9c2f0, #27431) - The compliance ledger's deployment-level rows (no
tenant_id) are served to the platform administrator alone, in every tenancy posture,singleincluded (for #26750) (2ecf6a7, #28047) - The draft preview refuses one column asked raw beside its own time bucket, as the live faces do (
c5ac1b6, #28794) ObjectFormlays a form out and no longer picks its container; theobject-formblock dropsformType: 'split'andSplitFormretires, while the block keeps openingdrawerandmodalforms itself (a33fce2, #27305)- An action's
newTabUrlis a path on this backend and must start with/; an absolute URL is refused at parse (c13ef04, #27252) - An object extension's
fieldPatchesrefuses a field the same extension replaces infields, keys option patches by the option-value grammar, refuses__proto__, and carries the field's and option's display renames (3fb74b6, #29331) - An inline action (
InlineActionSchema, a page button'sproperties.action) refusesconfirmTextbeside a non-emptyparams, as a registered action (ActionSchema) already does, instead of accepting a pair that opens two dialogs for one decision (2c881fb, #29829) - A page
action:button(ActionButtonPropsSchema) refusesconfirmTextbeside a non-empty arrayparams, as a registered action (ActionSchema) and an inline action (InlineActionSchema) already do, instead of accepting a pair that opens two dialogs for one decision (2bf71ce, #29869)
自动迁移 — converted for you by os migrate meta
41 breaking entries carry an ADR-0087 registration on the protocol-18 step,
49 registry ids between them, and os migrate meta does each one: it converts
what has one meaning and lists the rest for you. A live author of a narrowing
is refused at parse, with the prescription. os migrate meta --from 17 lists
the edits for your sources (--write applies the ones it can trace to one
literal), and os migrate meta --stored lists and rewrites a deployment's
stored rows. The CHANGELOG entry says, for each, what is converted and what is
left to you.
| Change | ADR-0087 id |
|---|---|
Ownership queues: records wait in a queue until a member claims them, and assignment rules route new records (ADR-0142) (a9f654e, #26337) | object-queue-field-reserved |
bundled is the one plugin packaging strategy — manifest-deps is retired, and os plugin build refuses a native addon (for #27123) (fe9e856, #27679) | manifest-packaging-manifest-deps-removed, manifest-packaging-manifest-deps-retired |
An action's description, each param's helpText and each outcomeMessages entry are template slots; the doors refuse ${…} there (7e3822c, #28463) | action-dialog-outcome-copy-template-holes |
A template currency hole with no argument formats in the organization's default currency, and with none the render refuses it; nothing formats in US dollars by default any more (for #26939) (79ed6f7, #27249) | template-currency-no-argument-tenant-default |
A fixed currency config needs its code — currencyConfig.defaultCurrency loses its 'CNY' default, and a dynamic config parses with no code (0cd578f, #27218) | currency-config-fixed-default-currency-required, currency-config-dynamic-default-currency-removed |
The formula gate refuses bare date subtraction, which reads null or {} at runtime (51058ef, #29058) | formula-date-subtraction-refused |
dashboard-filter-binding-unknown refuses a widget filterBindings key that names no filter the dashboard declares, naming the widget, the key and the declared filters (0368504, #27527) | ui-dashboard-filter-binding-unknown-refused |
A field group's relatedList names body tables only — a group naming a side card or a hidden list is refused, and Studio offers body tables only (aad9ad5, #27592) | field-group-related-list-side-card-refused |
objectstack validate judges a connector_action node's connectorConfig.input as a value slot (3cc45cc, #28150) | connector-action-input-value-slot-judged |
An object that declares titleFormat is refused at save and by os validate, with the nameField prescription (for #29007) (3557366, #29499) | object-title-format-refused |
Retire the list view's options bag; stored bags fold onto the top-level blocks, and the renderers read the blocks alone (for #25923) (8eeb3b3, #27206) | list-view-options-bag-folded, ui-list-view-options-bag-folded |
A write naming no package onto an installed package's view, dashboard, report, translation or email template is refused where os migrate packageless-overlays can move its row as a declared override, naming the base's .custom pair and the command (bc02a77, #28850) | packageless-overlay-write-closed |
A user field's declared deleteBehavior takes effect when the user is deleted (for #29253) (fe5ed7c, #29368) | user-field-delete-behavior-enforced |
Retire the unserved Virtual ObjectQL request protocol from data/data-engine.zod.ts (9ac5ae5, #27324) | data-engine-rpc-protocol-retired |
The record page's slots.tabs is retired — a slotted page fills six slots, and a page that needs its own tab strip is kind: 'full' (6dbff8e, #27219) | page-slots-tabs-removed, page-slots-tabs-retired |
POST /analytics/query and POST /analytics/sql refuse an empty cube name with 400 VALIDATION_FAILED, not 500, and the engine's in-process guard throws the same typed refusal (for #27505) (e0aed00, #27691) | analytics-query-empty-cube-name-refused |
QuerySchema.fields (and so every nested expand entry) and the flow get_record node's fields refuse an empty projection, as every other published projection declaration and the in-process engine do (for #27166) (385aa12, #27244) | query-and-get-record-empty-projection-refused |
registerFlow refuses a malformed envelope and a {…} token in a connector_action node's connectorConfig.input (3cc45cc, #28150) | connector-action-input-value-slot-judged |
oauth2 and app-credential join declarative connector-instance auth, and the runtime oauth2 variant is retired (ADR-0143 S0, #28137) (3238750, #28213) | connector-auth-config-oauth2-retired |
A datasource named drivers is refused at every door that parses a datasource, not only at the admin create door (c9aaee9, #29423) | datasource-name-route-shadowed-refused |
A form view no longer declares drawerSide / drawerWidth; no form view opens in a drawer, so neither key was ever read there (b5e7ba5, #29287) | form-view-drawer-keys-removed, ui-form-view-drawer-keys-retired |
Shipped in 18.2.0 and listed one release late: A navigation group that says nothing opens — GroupNavItem expanded defaults to true (for #26427) (21a13f1, #26427) | ui-nav-group-expanded-default-open |
A record opens one way per object — navigation.mode keeps drawer (now the default), page, new_window and none; modal, split and popover are retired, and a list page's recordAction drops modal (17b5152, #26804) | navigation-mode-overlay-values-removed, ui-record-open-overlay-modes-retired |
An object that declares one action name twice is refused at every door that parses it, and Studio never writes the pair (cdd9f0a, #26874) | object-duplicate-action-name-refused |
The action blocks spell the destructive variant danger, the word ActionSchema.variant uses (fbe344b, #26966) | page-action-block-variant-destructive-to-danger, ui-page-action-block-variant-destructive-refused |
A form view's buttons block holds submit and cancel only; buttons.reset is retired, because no form ever drew a reset button (2655ebd, #27269) | form-view-buttons-reset-removed, ui-form-view-buttons-reset-retired |
A form view's type is its layout only — simple (the default), tabbed or wizard; split, drawer and modal are retired, and the split-only sections[].pane, splitSize and splitResizable keys go with them (14e9685, #27229) | form-view-presentation-types-removed, ui-form-view-type-layout-only |
CurrencyValueSchema and its CurrencyValue type are removed from @objectstack/spec/data (ADR-0104 D1) (b8f4acc, #27233) | currency-value-schema-retired |
The flow-node expression ledger roots a path in a node's sibling block, and declares a connector_action node's connectorConfig.input a value slot (3cc45cc, #28150) | connector-action-input-value-slot-judged |
An app opens on the first landing entry whose visible holds for the reader (044d201, #28496) | app-landing-url-item-refused |
A locale tag written in another case is corrected to its BCP-47 case — a translation saved as zh-cn reaches zh-CN sessions — and a translation bundle with two keys that fold to one tag is refused (86d0a8c, #29833) | translation-locale-case-canonicalized |
A dashboard that declares two global filters under one name is refused at every door that parses it (38a278b, #29162) | ui-dashboard-global-filter-duplicate-name-refused |
A view no longer declares itself personal — scope: 'personal' and sharing.type are retired, because nothing ever restricted a view to one user (85ea12f, #29447) | view-personal-declarations-removed, ui-view-personal-declarations-retired |
A dashboard global filter keyed dateRange beside a declared dateRange is refused at every door that parses it (c268b72, #29370) | ui-dashboard-global-filter-reserved-date-range-refused |
An object extension's fieldPatches is keyed by the field map's own key grammar, so a key no field name can be is refused where it is written (0e75081, #29513) | object-extension-field-patch-key-grammar-refused |
An object extension's fields is keyed by the field map's own key grammar, so a field no object could declare is refused where it is written (eeb3117, #29774) | object-extension-field-key-grammar-refused |
An authored connector entry refuses a key retryConfig or an actions element does not declare (1a8ba3f, #29573) | connector-entry-nested-undeclared-key-refused |
An authored connector URL holds no credential part, and the connector read judges every providerConfig and auth URL by the same walk (1a8ba3f, #29573) | connector-entry-url-credential-refused |
The connector write door refuses an inline secret on every instance auth kind; the metadata read redacts connector credential positions (672bda0, #28596) | connector-instance-auth-inline-secret-refused |
An authored connector entry refuses a top-level key it does not declare; the connector read withholds URL credential parts and a non-object auth (3e54f99, #28688) | connector-entry-undeclared-key-refused |
A URL is judged after trimming surrounding whitespace and removing tab and newline characters, as a URL parser does; a credential query parameter is refused in any string of a connector entry (cfd237f, #29875) | url-credential-judged-trimmed-and-in-any-connector-string |
新增能力 — new capabilities
The 307 entries that add to what an author or a caller can write. 114 of them
are graded minor, because they add to the metadata protocol.
Console and the @object-ui/* packages
96 entries.
- An action's param dialog names its own submit verb —
submitTextis drawn on the submit button in place of "Confirm" (bed8306, #28392) - An action's confirm and param dialogs name their dismiss button —
cancelTextis drawn in place of "Cancel" (462b4f9, #28983) - The Console is an installable PWA on desktop, with an offline app shell and a Service Worker ready for Web Push (
b772078, #29326) - Connector connections, a Setup page to connect, reconnect or disconnect each OAuth connector instance and see its status (
da46b2a, #28651) - Setup → Integrations → Connector Credentials, and a credential step after a marketplace install (
0c8c27e, #28200) - The workspace prompt also asks an administrator for a default currency that is still unset, offering the browser region's currency (
2ac698b, #28953) - A list view lets readers hide and show columns by default —
userActions.hideFieldsdefaults totrue(104cd1a, #29083) - A calendar opens on the date its
calendar.initialDatedeclares (71de060, #28334) - A kanban view's
kanban.lanesnow decides which lanes the board draws, and in what order (85a1b9d, #28291) - The OAuth consent screen explains the
metadata:writescope and never shows an unknown scope as its bare spelling (8222fa8, #26659) - The approvals inbox keeps a way back from a record, takes an optional reason on Reject, and marks requests you submitted (
af6ca00, #28415) - Native shell mode — the Console detects
window.ObjectStackNativeand talks to the ObjectStack mobile shell that hosts it (45e9b2f, #29140) - The settings hub tells a member that an administrator manages the settings (
b95b1ef, #29183) - A user with two-factor sign-in on is asked for their code after the password, instead of staying on the sign-in page (
d8b7ef4, #29338) /auth/me/permissionsis asked in the same wave asorganization/listwhen the session already names the active organization (5901871, #29302)- The sign-in card says no sign-in method is available instead of showing SSO-only copy when email+password is off and nothing else is advertised (
f2bf293, #29257) - A refused edit-form record read says a localized line, never the response body (
4e0ff3e, #29322) - The profile page shows the mobile shell's App lock and opens its settings screen (
b62463f, #29798) - The sidebar draws the report index only where the app's navigation declares it (
bf658d1, #27566) - The right slot counts the copilot's items awaiting confirmation, 「AI 副驾 · N 项待确认」 (
3cc4563, #27789) - After an approver's own decision ends their read, the record page says so instead of "Record not found" (
785a7c3, #26714) - On a deployment with no AI agent, Home's build chip opens Studio's new-package flow as "New in Studio", and
/aisays no assistant is enabled instead of redirecting home (74db43e, #26716) - The record page's related column loads counts first, then rows only for the cards in view, and never blocks the details (
8243694, #27636) - On a phone the record is always a page, with a compact header, a bottom action bar, and related records pushed as pages with a path row (
ee29ac3, #28477) - A plan that overlaps an existing app asks whether to extend it or build a new one, instead of offering Build it (
a552e86, #27595) - The record history "Changes" entry formats a currency, number or percent value the way the record page formats that field, instead of printing the raw stored number (
d220b9b, #27638) - The approval decision panel's record summary is headed "As submitted" (for #27502) (
144702c, #27635) - A packaged action switched off for this deployment is no longer offered as a live button (
9324232, #28115) - A
booleanfield draws a checkbox and atogglefield a switch, in the record form, the grid's inline editor and the Studio canvas preview (50d05cb, #27665) - A bulk result names the records it wrote, and a required-field refusal says what to fill in (
0ac6dab, #28378) - One top bar above the record header on every face — the breadcrumb on the left; the counter with ↓ ↑, ⊟, ⤢ / ⤡ and ✕ on the right, each only where it applies (
55a1feb, #28458) - Record pane v4 mechanics — the split pane beside the list, the non-modal drawer over it, ⤢ maximize in place, the drag range, and the list's narrowed faces (#27545, part A) (
c1b52de, #27953) - Record pane v4 in the console — a record opens beside the list by default, ⤢ maximizes it in place on the same URL, and the full page opens only from the row's ↗ and the record header's ⋯ (for #27545) (
277275a, #28129) - The Studio canvas runs a report or a dashboard widget over a draft dataset before the package publishes (for #27585) (
968cacf, #27758) - On a narrow window the sidebar is the icon rail, and a click expands it over the content (
8a1f467, #27893) - An approver chip that stands for several of a step's slots says so in words, so it reads with the eligible-approver count beside the tally (
a5d7098, #27699) - Entering an app loads its metadata bundle once, and a record open reads no metadata (
a9f1452, #28321) - A single-record action in a list's header is disabled, with the reason, unless exactly one row is selected, and is not drawn where the rows cannot be selected (
2e20454, #27894) - Switch a read-only package's flow off for this deployment from the Automations pillar (
86dc686, #28420) - A drawer or modal
object-formsave that answers warnings shows one toast, the record-named message carrying them (ba5c754, #28267) - A comment author is told, in one sentence, which mentioned users will not be notified (
be646ed, #28261) - A header action shows the reader already pressed it (
presseddrawn as aria-pressed) (for #28301) (7eda096, #28301) - A save's toast shows the notice a hook set on the write, still one toast per write (
b1e1dcf, #28307) - Create User left without an email marks the Email input as required, in the user's language (
9da2f4b, #28649) - A metadata create form states identifier errors in the reader's language, and its summary banner wraps them (
ac09e7b, #28395) - A stored view filter's OR group filters the list, and the filter builder saves and reads it (
768ec4b, #28475) - On a grouped list or a kanban board, the record pane's counter and ↓ ↑ step within the opened record's group or lane, and the counter's tooltip names it (
755794f, #28492) - A record open is one request — the discussion, the activity, the attachments and the related rows' names come with the page payload (for #28470) (
e7a6cc7, #28682) - The row and record-page Delete confirmations name the objects a delete will cascade into, from the platform's served
cascadeDeleteObjects(8c3f9d9, #28490) - A phone record page draws one bar, its own top bar, and the record's name once (
b15b634, #28883) - Each read-only base's card on the Studio landing says "n customized, m switched off" (
5e879ea, #28634) - An app page reads its views, its page and its nav dashboards from the app bundle, and reads the
/meta/view,/meta/pageand/meta/dashboardlists only for what the bundle does not hold (706ac52, #28754) - The installer OAuth client form shows the deployment's redirect URI, and a reconnect keeps the stored client unless it is replaced (
946a173, #29772) - Setup → Integrations → Connector Connections asks for the installer's OAuth client when an instance declares
clientSource: 'installer'(c88467d, #29496) - The import wizard no longer demands a required field that carries a default (
cae71fe, #28975) - The record's meta line leaves the owner out only when the highlight strip the page actually drew carries
owner_id, so a phone page names the owner somewhere (for #28909) (0be247c, #28994) - One "Personalized" marker beside the list title holds the one reset to the view (
8c93af7, #29266) - The impersonation banner names the administrator by name or email, never by a raw user id (
32b9c6b, #29272) - A record page whose comments or activity read fails (5xx, network) says it could not load them, with a Retry, instead of showing the empty state (
e5a24dd, #29358) - Inside the ObjectStack mobile shell, the header's organization button opens the shell's native connection switcher, and the Console answers the shell's
connectionRemovedevent (2420778, #29565) - A list's view switcher lists the reader's own views (My views), and any reader can save the list on screen as one, rename it, delete it and pin it as their default (
0b2b309, #29699) - The Studio item header says where a customization of a read-only base is saved (
97f8f6f, #29843) - The Studio landing gives a base's
.custompair no card of its own, and the pending-changes sheet says where a customization is saved (7f328cd, #29723) - The publish sheet speaks in the console's language, and a load error the server answered shows its code and sentence (
69dbb3c, #29780) - Photos, attachments and record sharing go through the mobile shell's
pickImage(),openFile()andshare()(a7e6e07, #29803) - A read-only flow node inspector says why its controls are disabled, on hover, on keyboard focus and to assistive tech (
fea2dbb, #29073) - "Reset to view default" clears the user's stored list setting instead of storing the view's value as their choice (
12aad14, #29044) - A record page opens from one request, and the record a drawer step shows next is asked ahead (for #27627) (
0027c4a, #27713) - A list block on a page caps its rows and ends with a "View all N" link; a tabbed list block caps each tab and badges each tab with its count (
dc9d1ff, #26700) - An ObjectForm block's
showResetdraws the Reset button it declares, instead of clearing the form after every save (9c51314, #27599) - A date typed in a form the picker cannot read is a field error that refuses submit, not a silent drop (for #27483) (
efa2915, #27623) - A filter row with no value is not a filter, a date sort reads Oldest → Newest, a sort keeps one row per field, and an empty list says how to start (
5da3a8c, #28423) - The edit form names the fields a visible update action would require, before the action is pressed (
4bc963c, #28459) - Right-clicking a list row opens that row's actions at the pointer; a row inside a multi-row selection opens the selection's bulk actions (
bede9de, #28457) - A record delete says it cannot be undone, and the success toast names the record (
c0f9fdb, #27850) ObjectStackAdapter.getRecordFieldAccessreadsGET /data/:object/:id/field-access(6866e2e, #27137)- Every record reference shows a hover card with the target's declared highlights, without navigating (
2f2c6b9, #27912) - Offer the mobile shell's code scanner on text and lookup fields (
ec2f5d5, #29493) - The Studio's navigation inspector names the report index face (
bf658d1, #27566) record:pathwithallowStageChange: truelets the reader press a stage to move the record there (be25e2d, #28303)- Over the 5000-record cap, the "select all matching" bulk bar says that only the first 5000 matching records will be processed (
d5da45b, #28606) - A list emptied by a filter the reader applied offers "Clear filters" as its primary action (
b889c40, #29047) - A
reportnavigation entry with noreportNameresolves to the app's report index (bf658d1, #27566) - The native bridge's
platformis an open set, and a bridge speaking a newer protocol reads as no bridge (9f7e408, #29475) RecordFieldAccessScopeanswers one record's field locks from the server's record-scoped projection (6866e2e, #27137)- The list toolbar offers the "In a queue" toggle only when the organization has at least one queue (
61de599, #28889) - A calendar view with no
endDateFielddraws no resize-end handle, and a drop that changes no date writes nothing (10bfd70, #29146) - An assistant answer's chart is still there after a reload (
9f76037, #27396) - A dataset measure declares its polarity, so a lower-is-better KPI draws a rising delta as bad news (for #26739) (
c26d643, #26739) - A related list's
filterand its add-pickerfiltertake a view filter's any-of group{ or: [...] }, as the spec carriers do (82b29fe, #28466) - The record history badge shows the action's option label, not the stored word upper-cased (
474b40e, #28989) - The import wizard's "Match on" no longer offers a field the server refuses as a match key (for #27567) (
4d1f52c, #27645) - The list's empty-state "Clear filters" also clears the user-filter chips, so a list emptied by a chip alone offers it (
7a221ac, #29189) - The filter-builder number bucket follows the spec's numeric value family, so
progress,sliderandsummaryfields get the number operators (9ab75ee, #27631) NavigationItem.reportNamesays that an omitted name opens the app's report index (bf658d1, #27566)DataSource.getRecordFieldAccessasks the server for one record's field projection (6866e2e, #27137)
Packages, overrides and installs
21 entries.
- A doc book's title and section titles are translatable —
books.<name>in the translation bundle (031d97a, #26819) - Export a deployment's whole ontology as installable packages in one read (
GET /packages/export,os package export) (b3d1415, #29755) GET /api/v1/meta/app/{appId}/bundleanswers an app's metadata in one request, per caller, with an ETag (for #27627) (8441a01, #28287)GET /packages/:id/customizationsanswers what this deployment changed on an installed base, in one read (519089e, #28595)- An object extension is an item of a writable package — saved, published and exported through the existing doors (
0c1829a, #28811) - One effective read — every read of a name a code package ships serves its
.custompair's declared override, the env-wide list and the export agree with it, and a withdrawn public form stays withdrawn (ADR-0146 D8) (eeec88d, #28507) GET /meta/objectserves which packaged actions are switched off beside the item, and a switched-off action is refused with a plain sentence (3e0fd7a, #27964)- A disabled package's export carries its items, so a re-install no longer loses them (
5afe7d7, #29914) - A tenant's rewrite of a packaged validation rule message is served as written in every locale, on the refusal and on
/meta(for #27221) (556f323, #27543) - Every item a composed stack declares only on its top level, beside package bodies that do not declare it, registers in the engine at boot the way a package body's item of that type does (
f9a1363, #28153) - Every served package row carries
disableable, the disable door's own verdict, besidewritable(8771726, #28371) - A package export carries its namespace and its dependencies, so a reinstalled
.custompair keeps both (a6a369e, #29651) - A translation bundle may leave out
apps.APP.label, so a module package can translate only the navigation ids it contributes into another package's app (8b1740a, #27203) PLATFORM_TOOLS_BY_PACKAGE['service-ai']listsread_package_docandsearch_package_docs(0fb0dda, #27011)- The object meta reads declare
disabledActions, the packaged actions the activation ledger switches off, beside the item (3e0fd7a, #27964) - One override declaration for code and Studio —
overrides: { package, basedOn }, the.custompairing helpers, the closed overridable-type list, the__creservation and the base-field display whitelist (ADR-0146) (ba0084e, #28467) - The body of
POST /packages/:id/publish-draftsis declared, so the change note a publish carries is part of the published API contract (462935d, #28447) - The owner-stamp build door judges a
packages[]artifact's top level the way the boot does (b399cb8, #29303) PackageExportBundleSchemadeclares the whole-deployment export, every installed package as installable packages in one read (f1d795b, #29100)- The package export manifest declares
withheldMaps, the maps it withheld whole, and an unmanaged import accepts it (45b4922, #29521) PLATFORM_TOOLS_BY_PACKAGE['service-ai-studio']listsdraft_record(99a6a78, #28131)
Metadata the build, lint and save doors now refuse
6 entries.
os explain appsays areportnavigation item may omitreportNameto open the app's report index (bf658d1, #27566)os plugin publishdecodes the artifact's compiled manifest once, throughreadCompiledPluginManifest(for #27914) (c4390f7, #28075)- The compiled plugin manifest's filename has one spelling, the runtime's (
50646f0, #28530) os migrate meta --jsonsays when no migration step ran (c749198, #29045)- Studio's publish takes a change note, every history row it writes carries it, and the item history names the person who changed it (
a8d2fd7, #28422) client.auth.mePermissions()reads the caller's effective permissions, typedMePermissionsResponse(8afaf12, #28766)
Flows, connectors and automation
38 entries.
- A hook (shipped body included) and a record-triggered flow can set the save notice the create / update answer carries (for #28251) (
a00f335, #28753) - The install-local answer lists the connector credentials the installed package names, and which are not set yet (
7735d1d, #27344) - A package export names each connector map it withheld, and both install modes of install-local name each one as incomplete (
e756782, #29746) - An OAuth-protected MCP server is connected through the MCP authorization flow and dispatched with each organization's token (ADR-0143 S2, part of #26702) (
7064615, #28437) - A credential the MCP server rejects is thrown as
ConnectorCredentialRejectedError, so the instance is degraded instead of failing the boot (7735d1d, #27344) signedjoins declarative connector-instance auth, withaliyun-acs3-hmac-sha256as its first scheme (ADR-0143 D2.3, #26710) (b358e79, #28273)- A stored connector under a host plugin connector's name is skipped at start, as it is while the platform runs (
60d8522, #28755) - A flow start input can declare that it is the id of a record of a named object (
objectName) (772b356, #29571) connector-credential-literal—os validate,os build,os lintand the connector save door warn on a credential-shaped literal in a connector's header or env map (aca58cd, #29424)- Both publish doors judge a stored flow draft in its D2-converted spelling, as the save door does (
0eb97fa, #29694) - A flow override declared in the shipping package's paired
<base>.customis the packaged flow — saved, served and armed in place of the base, and deleting it restores the base (for #28412) (3ee6af4, #28693) - A save of a connector's served form drops the leftover inline value under an undeclared
authkey, and the save answer names each key it dropped (7b293c4, #29456) - A declared
.customflow override is armed from the first boot pull, and a legacy package-less row of the same name is named in the boot's shadowing warning (for #28717) (8f294e0, #28770) - A flow read's
_diagnosticsand the restore doors judge a stored flow in its D2-converted spelling, as the save and publish doors do (0712470, #29753) - A flow a composed stack declares only on its top level, beside package bodies that do not declare it, registers with the automation engine at boot (
ae64120, #28091) - An admin can disable a
type: 'module'package of an installed app, and a disabled package's navigation contributions and record-change flows stop (for #28117) (7abe151, #28308) - Organization administrators hold
manage_connector_connectionsby default, so an organization's owners and admins can connect its OAuth2 connectors without a hand-bound permission set (ADR-0143 D5, ruling B) (8e87403, #28441) oauth2connector instances are served per dispatch, and an administrator connects anauthorization_codeone for their organization (ADR-0143 S1, part of #26702) (e01edb2, #28317)- An organization-scoped
manage_connector_connectionscapability gates the connector connection door (ADR-0143 D5) (9eaa209, #28400) GET /packages/:id/customizationsnames the package behind each field and flow another package contributes to the base (8196881, #28749)- Switching a packaged flow off now records which administrator did it (
8c0306f, #26605) GET/PUT/DELETE /api/v1/automation/connectors/credential-refs— list the connector credentials an environment's connectors name, and set or clear one (7735d1d, #27344)- A flow started through the REST or action door carries its caller's posture rung beside their positions and permission sets, so a platform administrator's manual run is served the ledger's deployment-level rows their request is served (for #28099) (
7d846b5, #28167) - A write in an action, hook or job body, or a flow record node, that landed under a failing roll-up answers its own success (for #28861) (
153869d, #29283) alipay-v3joins thesignedconnector-instance auth schemes, with response verification (ADR-0143 D2.3, #26710) (f79fa74, #28446)tencentcloud-tc3-hmac-sha256joins thesignedconnector-instance auth schemes (ADR-0143 D2.3, #26710) (5374967, #28279)- A paused screen says whether another screen can follow, and the screen-flow runner reads Next or Submit and counts steps from it (
e438a55, #26752) - An administrator sets a declarative connector's credential in the environment's credential store, and a missing one degrades its instance instead of failing the boot (
7735d1d, #27344) - A
runAs: 'user'run carries its user's posture rung, so a flow run as the platform administrator is served the ledger's deployment-level rows its user's request is served (for #28070) (cbb6df1, #28098) - A suspended run resumes on the flow body it started on, not on the definition registered under its name (for #28405) (
cf6c8c9, #28500) @objectstack/spec/dataexportsisListComparandOperator, and a flow's record nodes read it instead of a local copy of the list operators (df8d020, #29570)- The connector connection read answers the deployment's OAuth
redirectUri, and a client-less reconnect of an installer-client instance reuses the kept client (c779611, #29701) - An
oauth2authorization_codeconnector instance can take its OAuth client from the administrator who installs it —clientSource: 'installer'(d8c611d, #28920) IAutomationService.toggleFlowtakes an optional caller, so switching a packaged flow on or off can name the admin who did it (d9bf777, #26545)ConnectorCredentialRejectedErrorand the connector credential members ofIAutomationService(7735d1d, #27344)ConnectorUpstreamChangedError, the marker for an upstream that no longer matches the instance's pin (76b4ae4, #28515)ConnectorConnectionStatedeclaresredirectUri, the deployment's derived OAuth redirect URI (744211f, #29618)- The flow clone answer declares an optional
noticeCodebeside its Englishnotice(f87239a, #29779)
Templates, formulas and mail
14 entries.
os migrate packageless-overlaysmoves package-less Setup customizations of a package's view, dashboard, report, translation or email template into its paired.custompackage as declared overrides (63acf35, #28689)- Opt-in Cloudflare Turnstile on email sign-up — off unless both
OS_TURNSTILE_SITE_KEYandOS_TURNSTILE_SECRET_KEYare set (fc5d509, #28365) - A failed settings read no longer dead-letters a templated mail as
TEMPLATE_INVALID; only a read that finds no default currency is permanent (for #27257) (7739573, #27730) label(record.<field>)reads a select field's option label in the reader's locale, so a record name composed in a formula rendersv2 · 清稿to a zh-CN reader andv2 · Cleanto an en-US one (for #26608) (76a1275, #26755)- The mail hole grammar admits
currency:@path, which reads the ISO 4217 code from the send's data; the in-app template dialect refuses that form by name (2205863, #26937) email-template-hole-grammaracceptscurrency:@pathand refuses a marked path the template's declared variables do not declare (2205863, #26937)- The layered read takes the bundle member selector, so the layers of one locale of an
email_templatecan be read (0c47e87, #26861) - The layered read on a base's address answers an email template locale added in the base's
.custompair with the pair's layer (for #28830) (3e62a22, #28845) - An invite says whether its mail was delivered, and a pending invitation's link can be copied from Setup → Invitations (
f552fa8, #27902) - Setup Create User marks Email required while phone sign-in is off (
8bceb6a, #28707) - A mail template formats an amount in the record's own currency with
currency:@path(2205863, #26937) - Every email template can read the deployment's address as
{{baseUrl}}(73c2439, #29478) EmailSenderSurface.sendTemplateandrenderTemplatedeclare the optionalcurrencythe email and inbox channels already send (f5e6709, #28523)- The
email_templatefaces state the mail hole grammar, includingcurrency:@path, which reads the ISO 4217 code from the send's data (2205863, #26937)
The data engine and field values
23 entries.
- Backfill the currency code of rows stored before an amount field was switched to
currencyMode: 'record', and acknowledge the switch at the metadata save (a3d24c9, #28128) - One spelling of the residual-picklist refusal, and one envelope for two registration refusals (
1c61f48, #28531) data.fieldAccess(object, id)reads the field projection of one record (f9f27a0, #27063)- The push device registry is served:
sys_user_device,POST /api/v1/notifications/devicesandDELETE /api/v1/notifications/devices/:deviceId, and the client'snotifications.registerDevice/unregisterDevice(ADR-0012 §6) (7b55ea9, #29628) - An import row with several bad cells reports every one of them in
errors(for #25582) (2f21b48, #26543) - A cleared optional single-value
selectorlookupstoresnull, so a second record cleared on a unique one is no longer refused409 UNIQUE_VIOLATION(bd68248, #29464) - A cleared optional multi-value field stores
null, so a second record cleared on a unique one is no longer refused409 UNIQUE_VIOLATION(8209a2f, #29837) - A unique field holding
nullin two records is admitted, as on driver-sql and driver-memory (5924e50, #29339) - An OData v2 service is served by the same driver with
version: '2.0'— the verbose JSON envelope,$inlinecount, typed literals,MERGE, and$metadataAssociations read into the lookups an import drafts (d359857, #28177) - A record-currency amount is refused without its code, refused in an ungrouped raw aggregate, and refused under a summary roll-up (
57c51aa, #27339) - A form's thank-you panel can name the record's engine-generated fields, and the public submit answer has a declared shape (
9b1dc99, #28440) - A field marked
searchable: trueis searched when its object declares nosearchableFields(29c70ca, #29385) - A count roll-up saves without
summaryOperations.field(fbb8bec, #27533) - A predicate delete walks the rows it matches as one set: each relation pointing at the object is asked once per 500 matched rows, not once per row (for #27004) (
693a729, #27073) - A record-mode amount that declares
currencyConfig.homeAmountstores its value converted into the home currency, with the rate used, on every write (ADR-0144 D5) (6762dd9, #28302) - A conformance kit a plugin driver outside this repository runs in its own CI, so a change to the contract the engine holds plugin drivers to is caught there (
d8de26d, #27798) - An update that carries a stored reference back unchanged is no longer refused because that reference has since stopped resolving (for #29062) (
344a16d, #29245) - A
referenceNameVisiblelookup or user field names the record it points at on$expandwhen the reader may not read that record (b861525, #29576) getRecordFieldAccess— a record's field projection from the write gate's own verdict (f9f27a0, #27063)GET /data/:object/:id/field-accessserves the field projection of one record (f9f27a0, #27063)- The public form submit answer carries the engine-generated fields its thank-you panel names (
93c0a63, #28479) - Shipped in 18.2.0 and listed one release late: ImportRowResultSchema declares errors, every cell error of a failed row (for #26454) (
43714ca, #26454) - A record-scoped field projection, judged on the stored row by the write gate (
f9f27a0, #27063)
REST, MCP and query parameters
19 entries.
- The ownership refusal
os startprints names the auth secret the ceremony needs (790d9cf, #29913) POST /api/v1/data/{object}/{id}/pageanswers everything a record page asks when it opens, in one request (for #27627) (0027c4a, #27713)- Every 2xx write answer whose body reports
droppedFieldsalso sends theX-ObjectStack-Dropped-Fieldsheader (078e581, #29034) providerConfig.toolsSnapshotpins each tool of anmcpinstance by name (76b4ae4, #28515)- A stdio MCP server takes its credential from the secrets layer through transport.credentialEnv (for #29621) (
dd03ee0, #29621) - An agent connected over OAuth is refused by name before an api action's forward, and
list_actionsmarks such an action instead of the target answering it as anonymous (776c1d8, #28442) validate_expressionrefuses thelabel()call the object save door refuses (fc28644, #28774)- A draft's per-key provenance records writes made through the MCP build door, and the OAuth client that made them (
8d79bbd, #27220) - The two
action_invocationrows of anapiaction that forwards to another action route name each other (125ad06, #28389) - An AI agent connected over OAuth can run
apiactions that forward to identity routes and other same-origin routes, as the agent (d3fdd46, #28482) GET /auth/me/permissionsanswershasAnyQueue, whether the caller's organization has at least one ownership queue (3944d2e, #28654)- A strict response contract for
GET /auth/me/permissions, andhasAnyQueueon the ownership-queue contract (4d278c9, #28704) - The default CORS policy lets
If-None-Matchthrough and exposesETag, so a cross-origin browser client can make the conditional requests the metadata doors speak (93153f0, #29092) - The record page payload carries the record's discussion, activity and attachments, and the related rows' names (for #28470) (
e7a6cc7, #28682) - The record page payload serves the record's 动态 count, so the activity part asks only the panel's rows (for #28737) (
c708de6, #28991) - A hook that aborts with free text answers the data door with
HOOK_ABORTEDinstead of nocode(21c4bdd, #27667) - An idle notification or HTTP dispatcher tick reads the database twice, instead of 18 and 10 times (
a727ba2, #28403) - Declare
features.ssoon theGET /api/v1/auth/configresponse (aee9c47, #29455) - Declare the nine remaining served
featureskeys on theGET /api/v1/auth/configresponse (4b06a54, #29583)
Analytics
9 entries.
- A preset
dateRangeon adatefield counts the reference time zone's calendar days on the in-memory analytics face (bc4c963, #27341) - A report chart can split its bars by a second dataset dimension with
chart.seriesBy(8d36669, #28191) - A host-configured default and maximum page size for list, report and analytics reads, and a per-query row budget, refused with a code that names the limit (
b64980a, #29796) - A dataset measure declares its
polarity, so a lower-is-better KPI draws a rising delta as bad news (c26d643, #26739) - Analytics over a record-currency amount — grouped answers name a per-row currency, an ungrouped amount aggregate is refused, and two compile-time guards on record-mode measures (ADR-0144 D2, D6) (
3604371, #27338) - A windowed dataset answer reports its window in
drillRanges, so a drill list stays inside the picked period (12b5aec, #27169) - A dataset
countthat names an amount field is no amount: the cross-currency guard no longer compares it, and its result column carries no currency (d35db2c, #28360) - The draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do, and the ObjectQL strategy serves a backed
RELATION.iddimension (92a1638, #28742) - The ObjectQL strategy serves two dataset dimensions over one column (
52de5a5, #28762)
Security, identity, sharing and approvals
19 entries.
- A comment's author is told which @mentioned users will not be notified, and a mention added by editing is notified (
7811c3f, #28174) - An action run through MCP leaves an audit row naming its surface (
3a054cb, #28171) - Switching a packaged action or a security catalog item off now records which administrator did it (
a98693d, #26551) - A unique refusal tells the user which field by its label, never by its API key (
5aad4e0, #26934) - One reader decides whether an external object's owner is provable, and record sharing and lint both read it (
684f2f6, #27794) - Warn when a permission set's own View All or Modify All makes its field mask inert (
51a12c0, #29724) - The HTTP MCP runtime bridges a host-injected authoring
ToolRegistrybehind a newmetadata:writeOAuth scope, with door provenance on every call (9bf6598, #26550) - An action on a
sys_*object whose author exposed it to AI is listed and run over MCP, behind every gate the action already has (bc496bf, #27940) - The stored-metadata healing pass re-saves a draft-only permission set that grants on an object which exists nowhere now, instead of reporting it
failed(f2f6bf0, #29350) - A membership-tier change that grants an org-admin permission set says which set it granted (for #27862) (
5954705, #28086) - The approvals get serves a request's own route, and both approval step lines draw it (
9fab076, #26976) - A
demoStaffentry names its line manager, and the development boot writes it (13e85be, #26763) - A session stores its client address in RFC 5952 text, and is born with
last_activity_at(cb8988e, #27003) - A sign-in that skips the OIDC seams can apply the IdP locale rule through
AuthManager.applyIdpLocaleClaim(da7bcc8, #27674) SharingService.admitsByIdWrite— the by-id write gate the middleware refuses on (8bb2c31, #27232)- A forwarded
type: 'api'action keeps the target's whole refusal, and an approve or reject carrying decision outputs passes the param check (e8cdf4e, #27915) - A non-admin approver's decision from the request page reaches the approvals routes instead of answering 404 (for #28657) (
0c6b7b8, #28708) - The push channel's protocol face, declared ahead of its producer: the widened device registration request, the stored device shape, optional
INotificationServicedevice members, the push delivery data with its same-serverroute, and the APNs / FCM transport configuration bycredentialRef(ADR-0012 §6) (e9fb529, #29218) - A shared NULL-distinct
uniqueconformance case-set, so every driver proves two NULLs do not collide (31204d1, #29808)
Datasources
10 entries.
- A datasource defined in code reads its credential at connect from
OS_DATASOURCE_<NAME>_PASSWORD(e063a87, #28208) os servepublishes its datasource secret store'sresolveas the kernel servicedatasource-secret-resolver, anddriver-odatareads it when it is passed no secret store (0a52b7d, #27952)- An install-local install judges who holds a datasource name with one judge that reads the engine too and fails closed, and a restart's rehydrate no longer takes over a name another holder owns (
df34d37, #28418) - Import objects from an OData datasource's
$metadata— EDM types map to field types, the key to the id, and navigations to lookups; Studio's datasource page imports several entity sets at once, and MCP gainslist_remote_tables/import_object(d3b0a40, #27880) - A datasource that names a connector instance in
external.connectorborrows that instance's credential at runtime, and its driver presents it per request (ed8b237, #27819) - The engine's floor over a plugin driver reads the datasource's
external.rowCap, so a whole-set view over a virtual object can match up to 10,000 rows (for #29762) (2ae8c89, #29876) - A plugin-driver datasource refused at boot for its connector instance's credential recovers on the next granted borrow, and the connector call path withholds the form-encoded spelling of a credential (
00c9a0b, #27990) - An API-backed datasource names a declared connector instance in
external.connectorand borrows its credential (b373263, #27783) - A plugin-driver datasource can declare its row cap,
external.rowCap, up to a platform maximum of 10,000 (for #29762) (a4daa85, #29854) - A
userfield over a text column passes the external-datasource type check; ADR-0015 names theodatavocabulary (f934a41, #28011)
Schemas in @objectstack/spec
52 entries.
- A joined report's block headings and a master-detail field's inline grid heading are translatable (
4f05de4, #26757) - A hook declares its refusal catalogue, and its body refuses by code, answered in the caller's language (
b3f7344, #26831) - A greyed action says why —
disabledReasonbesidedisabled(13dc413, #26786) - A form view's thank-you panel is translated through
objects.OBJECT._views.VIEW.submitBehavior.title/.message(7c3cc88, #28195) - A page
object-form's thank-you panel is translated throughpages.PAGE.components.ID.submitBehavior.title/.message(361ea24, #28269) sys_personal_view, a reader's own named list views (server step of the first version) (4008e62, #29575)- A hook can tell the person who saved a record what the save set in motion (
e26cf9a, #28247) - A dismissed or revoked suggested audience binding can be re-opened, back to pending (
efdf11a, #27353) - The built-in
restandopenapiproviders serveoauth2andapp-credentialinstances through the per-dispatch credential handle, and the Feishu, DingTalk and WeCom app-credential exchanges run (ADR-0143 S2, part of #26702) (7e9f814, #28399) - A declarative
openapiinstance takesproviderConfig.include, an operation allowlist (7f17135, #27387) - A platform administrator sees who holds platform-admin standing from Setup → Users (for #28793) (
92b7bbd, #29427) - A lookup field declares which fields a pick fills (
lookupAutofill) (c3842ac, #28244) - A
percentfield declares its read-only face withpercentDisplay(barorplain) (5b7dbb8, #28222) - A lookup can declare that its target's name is visible to every reader of the record (
referenceNameVisible) (9467a1b, #29379) - A report declares the period it covers (
dateRange) and the export of its table (exportOptions) (bb91ae8, #26628) - An object declares the business order of its related lists (
relatedLists), andrelatedList: 'primary'places a list instead of ordering it (c63ce2a, #27215) - An action can show the reader already pressed it —
pressed(916531a, #28235) - A kanban board can choose which lanes it draws, and in what order,
kanban.lanes(3739d8d, #28196) - A stored view filter list can hold an any-of group,
{ or: [rule, ...] }(f4a8eed, #28380) - An app can name a landing per audience with
landing(80a9f99, #28449) - An inline action can carry
description, the line its param dialog shows under the title (d68d129, #29369) - A tenant's rewrite of a validation rule that an object extension contributes is served as written in every locale, on the refusal and on
/meta(for #27559) (4a5396b, #27932) - A record page can name the apps whose routes open it for its object (
PageSchema.apps) (b16ee8b, #29437) - The object read names the objects a delete can cascade into, beside the item (
6e0b16a, #28451) currencyMode: 'record'+currencyField— a record-mode amount points at a sibling ISO 4217 code field (ADR-0144 D2) (4145210, #27268)- The mobile sign-in hand-off — a sign-in completed in the system browser is handed to a registered app scheme as a single-use token, over better-auth's own
one-time-tokenplugin (2b15e30, #29766) getRecordFieldAccessanswerswritable, from the write gate run over an update of the row (8bb2c31, #27232)os explainno longer tells a Modify All holder that a row-conditioned field lock binds (fc55892, #29637)- Shipped in 18.2.0 and listed one release late: A scheduled run whose due time passed while no kernel was ticking is recorded as missed, never replayed (for #26485) (
2282aa8, #26485) - A record prints on any print page of its object, not only on the default record page (
5dcbdb4, #26662) - The default currency (
localization.currency) is locked once an exchange rate or a home-currency amount uses it, and the Localization page names what uses it (ADR-0144 D4) (291bf44, #28297) currencyConfig.homeAmount: { field, rateDate, rateField }— a record-mode amount declares its stored home-currency amount by pointer (ADR-0144 D5) (73b7270, #28096)- An inline
object-metrictile declarespolarity, so a lower-is-better KPI draws a rise red (dbf576b, #27361) - The record field-access answer says whether the record itself is writable (
8bb2c31, #27232) - A
reportnavigation item withoutreportNameopens the app's report index (bf658d1, #27566) - The customizations read's
contributionsdeclares anavkind, with an entry shape per kind (0c64014, #29401) - An action's param dialog can name its own submit verb —
submitText(a7bdac4, #28188) - A calendar view can declare the date it opens on,
calendar.initialDate(8f36f6b, #28138) - A
record:pathblock may opt in toallowStageChange, so a stage can be pressed to move the record there (fb4e0bb, #28198) - An action's confirm and param dialogs can name their dismiss button —
cancelText(76bf6c5, #28859) - An object extension can carry a per-field display patch (
fieldPatches), its keys derived fromBASE_FIELD_DISPLAY_WHITELIST; not applied yet (5114b0e, #28650) GetMetaItemsRequestSchemadeclaresnames, the batch by-names read of a/meta/:typelist (6f09464, #29722)- The name-only reference value a refused reader is served has a named schema, and
referenceNameVisiblesays which name it carries (87956dc, #29623) - Push delivery data names the server that sent it —
RegisterDeviceRequest,UserDeviceandPushDeliveryDataeach gain an optionalorigin(5c4e752, #29767) - A cleared optional multi-value field is stored as
null, the same as one never set;FieldSchema'smultipleandrequireddocs now say so (4ab2e7e, #29763) - Declare the mobile sign-in hand-off surface (one-time-token hand-off and verify, and the registered app-scheme list) (
55a4898, #29804) - The push channel's hosted relay transport —
PushServiceConfigSchemabecomestransport: 'relay' | 'direct', with the relay as the default, and the server-to-relay wire shape is declared (api/push-relay.zod.ts) (1bb9536, #29886) - The
/layersanswer declares the pending draft as a layer,draft, as stored (c1af99c, #29863) - The error-code ledger registers
ERR_EXCHANGE_RATE_MISSINGandERR_HOME_CURRENCY_UNSET, and@objectstack/spec/dataexportsCURRENCY_FRACTION_DIGITS(ADR-0144 D5, Open items 3 and 9) (6762dd9, #28302) - A lookup pick fills the fields its
lookupAutofilldeclares, in the record form and in the line-item grid (ea1751b, #28322) ExplainAccessRequestdeclaresrecordIds, the batch selector the explain wire schema already carries (5d46f43, #29470)validateControlFlowthrowsFlowRegionShapeError, carrying the faulty container's path and node id (eb05ea2, #29789)
语义变化 — semantic changes
The 7 entries that declare Metadata-protocol: semantic and break nothing.
Each changes what a key that already exists means, or what it defaults to;
read the entry before you rely on the old reading.
- An object extension's
fieldPatchesis applied on every object read; a patch naming a field or option its target lacks is refused at install and byos validate(e62dcc5, #28728) requiredPermissionson atype: 'api'action — a same-origin api action is gated at the platform action door (ADR-0066 D4 amended 2026-10-29) (86c9d53, #27664)- A
connector_actionnode evaluates a CEL envelope inconnectorConfig.input, so a flow can send a per-record value to a connector's action (a0cc51f, #27980) - An interface page's list toolbar reads the published
userActionsdefaults — Filter, Row height, Group and Hide Fields are on unless the page writesfalse(ba1f164, #29299) - A form view's
layout: 'horizontal'draws each label beside its field, a tabbed form keeps it, and the form page drawstype: 'tabbed'as tabs (for #26843) (2d05cd4, #27222) - Every console form entry point renders the form view's
buttons,layoutanddefaults, and a tabbed form with subforms opens on itsdefaultTab(for #26843) (6af79c1, #27027) - A public form's thank-you panel shows the record field values its
{{record.field_name}}tokens name (1d201b0, #28493)
其他修复 — other fixes
The 760 entries that declare no breaking change and no protocol kind. 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 | 363 |
| Packages, overrides and installs | 54 |
| Metadata the build, lint and save doors now refuse | 21 |
| Flows, connectors and automation | 58 |
| Templates, formulas and mail | 26 |
| The data engine and field values | 38 |
| REST, MCP and query parameters | 21 |
| Analytics | 9 |
| Security, identity, sharing and approvals | 57 |
| Datasources | 11 |
| Files and storage | 3 |
Schemas in @objectstack/spec | 99 |
Among them, the ones that bear on an upgrade:
- Stored values.
os migrate blank-single-values(5bd615e, #29615),os migrate option-codes(9b4fa6c, #29700) andos migrate text-scalars(2545021, #29776) rewrite values older releases stored in a form today's write door no longer produces, andos migrate audit-key-material(a5df094, #26772) removes the key-material columns 18.2.0 and earlier copied into the audit ledger. - The sweep's fixes. A record page with no declared record page shows a
tracked record's field-change history again (
a7e984a, #29917), and a dashboard draft the server refuses to read stays unopened in Studio Interfaces (40a35de, #29922). - The packageless-overlay move.
os migrate packageless-overlaysalso moves rows bound to the read-only package itself (91f6a0b, #29477) and email-template locales the base does not ship (a19759a, #29258).
Console (Studio)
18.3.0 is the second release that 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: 544 of the 1,379 entries touch an
@object-ui/* package, 39 under 需手动处理, 14 under
自动迁移, 111 under 新增能力, 4 under
语义变化 and 376 under 其他修复. What a
Console user notices first:
- The record page. Details without frames, one timeline, related cards
the reader arranges and enlarges in place (
eef8373, #27310); one layout that follows the page's width, with 「动态」 (Activity) as the right column's first panel (3d2da4f, #28116); a tab row that closes the header (bf74607, #28394). The record opens in the drawer from a list, and the split pane beside the list is retired (ea150b7, #29108). - Studio and customization. A read-only base and its
<base>.custompair are one surface, with "customized" and "added" marks and New landing in the pair (7253344, #28747). A view, dashboard, report or flow of the base is customized into the pair, with an override badge and "Restore package original" (d62d973, #28711;693e100, #28824). Studio names an item's state with one word everywhere (e9e31e9, #29784). - Packages that leave npm.
@object-ui/app-shellis no longer published (6a7c0fe, #26483), and@object-ui/plugin-aiand its three node types are retired (c59b1bf, #27042). A host that imported either keeps 18.2.0 of it, or moves off it.
Upgrade checklist
⚠️ For an 18.2.0 app. From 18.1.0, walk the 18.2.0 checklist first, and so on back to the 18.0.0 checklist from 17.x.
18.3.0
How far this list has been walked. Not at all. It is compiled from the changesets, and no app has been moved from 18.2.0 to 18.3.0 for this page. Each step names the entry it comes from; read that entry for the exact refusal.
1. Move the pins
- Move every
@objectstack/*dependency, and every@object-ui/*one, to18.3.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. - Drop
@object-ui/app-shelland@object-ui/plugin-ai, which do not publish 18.3.0 (6a7c0fe,c59b1bf). - Rebuild a plugin packaged
manifest-depsasbundled:os plugin installandos plugin publishrefuse a stale.ospluginwith the schema's prescription (fe9e856,47c3e12,ed0e71d).
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 override of a base package's item, or a
__cfield, written anywhere but the base's<base>.custompair (d6e0967,447a2a6); - a record
navigation.modeofmodal,splitorpopover(17b5152), and a form viewtypeofsplit,drawerormodal(14e9685); - a list view's
optionsbag (8eeb3b3), a record page'sslots.tabs(6dbff8e), and an object'stitleFormat, which takes thenameFieldprescription (3557366); - a
${…}token in an action'sdescription, a param'shelpTextor anoutcomeMessagesentry (7e3822c); - a
fixedcurrency config with no code (0cd578f), and bare date subtraction in a formula (51058ef); - a flow loop not closed by a
type: 'back'edge, or a malformed loop, parallel or try_catch region (5f7d554,0814b89,dd4a21f); - an app navigation
visiblethat does not parse, and a page variable that is not unique, named and seeded (256c86b,d9c4cdc); - an inline secret on a connector instance, a credential in a connector URL,
or a key a connector entry does not declare (
672bda0,1a8ba3f,cfd237f,3e54f99); - a validation rule,
requiredWhen,readonlyWhen, optionvisibleWhenor hookconditionthat orders a text or boolean field against a number (4c2cd29, in the code but not yet in the CHANGELOG): writesize(...), a number field, or a cast only when every value is numeric text.
3. Before the first boot
- Move each connector credential out of its metadata. An inline secret on
a connector instance is refused at the write door (
672bda0); an administrator sets the credential in the environment's credential store, and a missing one degrades its instance instead of failing the boot (7735d1d). Anauthorization_codeinstance is connected per organization (e01edb2). - Give each organization a default currency if a mail or in-app template
formats a
currencyhole with no argument: with none, the render refuses it (79ed6f7).
4. After the boot
- Run
os migrate meta --storedto list and rewrite the stored rows the conversions touch. - Run
os migrate packageless-overlays, then with--apply, to move the customizations Setup saved without a package into each base's.custompair (63acf35,91f6a0b,a19759a). Until a row is moved, a write that names no package onto it is refused (bc02a77,838a802). - Run the stored-value commands, a dry run first and then
--apply:os migrate audit-key-material(a5df094),os migrate blank-single-values(5bd615e),os migrate option-codes(9b4fa6c) andos migrate text-scalars(2545021). A field switched tocurrencyMode: 'record'takesos migrate record-currency-codes(a3d24c9). - Update API clients for the doors that now refuse what they used to
ignore: an unknown or bracketed query parameter on the package, draft,
/metaand list doors (e1ab32e,96e7a43,d659c1d,08610a9,186a83a), an empty projection or search-field override (4702c46,86f2afe), and an unknown key on the datasource and analytics bodies (819b101,fa5e863). Each answers400naming what it refused. - Check who an approval, a sharing rule or a recipient reaches past
10,000 holders, and that a failed membership read now refuses instead of
answering nobody (
2789ba7,9bdfe37,e0187d0).
v18
The v18 line and its GA release, 18.0.0, which closes the `18.0.0-next.N` prerelease train and publishes to npm's `latest` tag. It carries ADR-0131 (organization ownership is total, with no NULL `organization_id`) and landed its breaking changes in stages, each with its own migration.
18.2.0
Release notes and upgrade checklist for 18.2.0, the first release that ships the Console and the @object-ui packages from this repository. A minor that carries breaking changes: one access rule for file references, app automation refused identity writes under elevation, installs that check declared dependencies, action copy refused at publish, and seven breaking changes that os migrate meta carries.