diff --git a/content/docs/releases/v18/18-2.mdx b/content/docs/releases/v18/18-2.mdx
new file mode 100644
index 00000000000..558100c7e93
--- /dev/null
+++ b/content/docs/releases/v18/18-2.mdx
@@ -0,0 +1,1025 @@
+---
+title: 18.2.0
+description: "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."
+---
+
+
+**How this page was compiled.** From the 231 changesets the version commit
+`27f38ada49` (#25640) consumed, each placed by `classifyReleaseBullet` in
+`scripts/release-github-releases.mjs`, the function that groups the GitHub
+Release body. The five sections below use its five headings, in its order, and
+its counts: **52** need you to act, **7** are converted for you, **41** add
+capabilities, **3** declare a semantic change, and **128** are other fixes.
+Every changeset is listed once, under its section. The version commit wrote an
+`## 18.2.0` section into 111 CHANGELOGs, and the same function places each of
+their distinct entries in the section this page lists it under. Each package's
+Release also carries an `Updated dependencies` bullet under 其他修复, which this
+page does not repeat. Four changesets merged while the version pull request
+was open are not in 18.2.0 and wait in `.changeset/` for the next release:
+the navigation group default (#26427), missed scheduled runs (#26485), every
+cell error of an import row (#26454), and the copy button on keyboard focus
+(#26487). The
+[upgrade checklist](#upgrade-checklist) is compiled from the changesets and
+has not been walked.
+
+
+## Highlights — 18.2.0
+
+- **The Console and `@object-ui/*` ship from this repository.** objectui became
+ the `ui/` workspace of this repository (`fd40831`, #25825, for #25752), and
+ `@objectstack/console` is built from `ui/apps/console` at the commit it
+ releases (`2a61f08`, #25745). There is no objectui pin any more:
+ `dist/.objectui-sha` and `dist/.objectstack-injection.json` leave the
+ Console tarball. The 37 public `@object-ui/*` packages join the
+ `@objectstack/*` fixed group, so 18.2.0 is the first release in which both
+ scopes publish at one version. See [Console (Studio)](#console).
+- ⚠️ **A minor that breaks.** 59 of the 231 changesets are declared breaking:
+ 15 graded `minor` because they narrow the metadata protocol, and 44 graded
+ `patch` under the level rule (#25105, ruling A). 52 of them need you to act;
+ 7 are converted by `os migrate meta`. Read
+ [the version number](#the-version-number-is-not-the-signal).
+- **One access rule for a file reference.** A record write claims or copies
+ only a file its caller may read, and is refused at the field when it names
+ one it may not (`3761710`, `7e4af20`). An unclaimed file is served only
+ inside its organization (`3b8ea15`), a released file keeps its record's rule
+ (`d9e6ac2`), and no anonymous write carries a file (`e2b40a2`, `b4199c9`).
+ A seed can now ship a file with `seedFile(path)` (`2163e80`).
+- **App automation cannot write identity rows under elevation.** A
+ `runAs: 'system'` hook, an action body, an inherited system context and
+ `ctx.api.sudo()` are refused writes to position holdings, permission-set
+ grants, invitations and memberships, as a system flow already was
+ (`073f08b`, `6a6ebff`), and a hook or action body that writes one in its
+ source is refused at save (`07586f5`). A permission set that only shares a position's name
+ is no longer granted to its holders (`733b3c8`): name it in the position's
+ `permissionSets`. An environment that runs HotCRM takes HotCRM `v6.0.0` or
+ later first.
+- **Installs check what a package declares.** An install whose
+ `manifest.dependencies` the environment does not satisfy is refused
+ `422 DEPENDENCY_MISSING` (`3446d43`), and a module that declares its app
+ may share the app's namespace (`be78977`).
+- **Action copy is judged at publish.** An action's `confirmText`,
+ `successMessage` and `errorMessage` are `template` slots (`384bb7b`), and a
+ blank sentence, an unbound root or a `${…}` token the runner shows raw now
+ fails `os validate`, `os build` and the publish gate (`0c81694`, `d727348`).
+- **Pinned means pinned.** `@objectstack/cli`, `@objectstack/hono` and
+ `@objectstack/plugin-dev` publish their `@objectstack/*` dependencies at
+ exactly their own version (`f4860da`), so the override the 18.1.0 checklist
+ added is no longer needed.
+- **`kind: 'react'` pages are off unless the deployment turns them on** with
+ `OS_PAGE_REACT_ENABLED=true` (`5e7cf63`), and the Console document carries a
+ nonce-based content-security-policy (`3c30afd`).
+- **New to author:** a field permission `when` (`c8cb510`),
+ `controlledByParent.ownerMayEdit` (`5e113b3`), a flow's
+ `requiredPermissions` (`f1ddace`), `demoStaff` (`f00e8d5`), duration
+ measures (`d1663ba`), the `$now` comparand (`be531f6`), and translation keys
+ for positions, permission sets, reports and form entries.
+
+---
+
+## Release facts, as measured
+
+Read from the version commit `27f38ada49`, its CHANGELOGs, and `npm view` on
+2026-10-27:
+
+| | Measured |
+| :--- | :--- |
+| Version commit | `27f38ada49` (#25640) |
+| Published | 2026-10-27 |
+| Packages | 110 publish at 18.2.0: 72 `@objectstack/*`, `create-objectstack`, and 37 `@object-ui/*`. The VS Code extension (`object-ui`) takes the version and is private |
+| `@object-ui/*` before this release | every one of the 37 reads `latest` `18.1.0` on npm, published from the objectui repository |
+| `@object-ui/sdui-parser` | not released. It is deleted from the workspace (`80f267d`, #26343); its 18.1 line on npm is its last. Use `@objectstack/sdui-parser` |
+| `next` | `18.0.0-next.5` on `@objectstack/cli` and `@objectstack/console`, **older** than `latest`. ⛔ Never install `@next` for 18 |
+| `rc` | `17.0.0-rc.6` |
+| CLI dependencies | the 49 `@objectstack/*` dependencies of `@objectstack/cli` are declared `workspace:*` and publish as exact versions. At 18.1.0, eight were `workspace:^` and published as caret ranges |
+
+Two notes for reading the 18.2.0 CHANGELOGs:
+
+- **Each entry names its own commit.** Every 18.2.0 CHANGELOG bullet opens with the commit that added its changeset, as the generator
+ has done since #25804. This page cites the same commits.
+- **objectui's last 65 changesets are one entry.** The changes objectui made
+ between its 18.1.0 release and the import came across under
+ `ui/.changeset/`, which `changeset version` does not read. One changeset
+ carries them into this release (`9a0af4e`, #26068), and each of the 65 files
+ stays in `ui/.changeset/` as the full record.
+
+The Docker image was not read for this page.
+
+---
+
+## What's new in 18.2.0
+
+### The version number is not the signal
+
+Every package still versions in lockstep, and now that includes the
+`@object-ui/*` scope. 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.2.0 carries 44 changesets graded `patch` that say **BREAKING**, beside
+15 graded `minor`. Of all 231, 38 are graded `minor` and 193 `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 [#action-required]
+
+The 52 entries that are breaking and carry no ADR-0087 conversion. Each line
+names what you do; the CHANGELOG entry carries the full before and after.
+
+#### Security, identity and administration
+
+- **A permission set that shares a held position's name is no longer granted**
+ (`733b3c8`, #26064). A holder of position `sales_manager` is granted only the
+ sets the position's definition lists in `permissionSets`; the
+ `position_name_fold_grant` warning now logs `granted: false`. Fix: write
+ `permissionSets: ['sales_manager']` on the position. An environment that runs
+ HotCRM must be on HotCRM `v6.0.0` or later before it takes 18.2.0. Code that
+ builds `UserAuthzGrants` or `ResolvedAuthzContext` from `@objectstack/core`
+ by hand sets the new `directPermissions` (`[]` with no grant rows).
+- **A `runAs: 'system'` hook and an action body are refused writes to
+ `sys_user_position`, `sys_user_permission_set`, `sys_invitation` and
+ `sys_member`** (`073f08b`, #25954), with `403 PERMISSION_DENIED`, and nothing
+ is written. Fix: set the hook's `runAs` to `'user'`; move an action body's
+ grant into a `runAs: 'user'` flow; change invitations and memberships through
+ the organization endpoints, such as
+ `POST /api/v1/auth/organization/invite-member`, `cancel-invitation` and
+ `remove-member`.
+- **The same refusal reaches a hook that inherits a system context, a hook's
+ `ctx.api.sudo()`, and an action reached through `execute()`** (`6a6ebff`,
+ #25982). Fix: declare `runAs: 'user'` on the hook and leave `sudo()` out, or
+ use the organization endpoints. A hook a plugin registers in code with
+ `engine.registerHook` is not affected.
+- **On a runtime with no hierarchy resolver, a permission set that grants
+ `own_and_reports`, `unit` or `unit_and_below` is refused on publish**
+ (`b029c90`, #26305): `422 INVALID_METADATA` under the rule
+ `hierarchy-scope-capability-required`, and a warning on a draft save. Fix: set
+ the grant's `readScope` / `writeScope` to `own` or `org`, or run it where the
+ enterprise `hierarchy-security` resolver is mounted.
+- **A delegated admin's write outside their own scope answers
+ `403 DELEGATION_SCOPE_VIOLATION`**, not `403 PERMISSION_DENIED` (`495fa07`,
+ #26320). Fix: a client that matched `PERMISSION_DENIED` for these refusals
+ matches the new code too; its message is safe to show.
+- **Under a walled posture, `POST /api/v1/auth/admin/create-user` adds the new
+ user to the administrator's current organization**, and refuses
+ `400 NO_ACTIVE_ORGANIZATION` when there is none (`17f8217`, #25626). Fix:
+ switch to the organization the user belongs to, then create them.
+- **Admin create-user refuses a password outside the deployment's length
+ range** with `400 INVALID_REQUEST`, and a too-long one no longer answers
+ `PASSWORD_TOO_LONG` (`1d2a246`, #25927). Fix: send a password inside
+ `minPasswordLength` / `maxPasswordLength`, or `generatePassword: true`; match
+ `INVALID_REQUEST` instead of `PASSWORD_TOO_LONG`.
+- **`IdentityImportDeps.getAuthContext()` is required** (`e3e936e`, #26018), so
+ admin import-users sizes a temporary password to the deployment's range. Fix:
+ a host that calls `runAdminImportUsers` itself passes `getAuthContext`, for
+ example `AuthManager.getAuthContext()`. The `AuthPlugin` route is already
+ wired.
+- **The built-in `auth.invitation` mail requires `expiresAt`** (`d284ff3`,
+ #26072); a send without it is refused `MISSING_VARIABLES: expiresAt`. Fix:
+ code that sends `auth.invitation` itself adds the invitation's expiry instant
+ to the send's `data` as `expiresAt`.
+
+#### Files and storage
+
+- **An anonymous public-form submission carries no file** (`e2b40a2`, #25682).
+ `POST /api/v1/forms/:slug/submit` refuses a value in a `file`, `image`,
+ `avatar`, `video` or `audio` field with `400 VALIDATION_FAILED`. Fix: take the
+ file field off the guest-facing form, or leave it for the visitor to leave
+ empty; a visitor who must provide a file registers and signs in.
+- **A form open to anonymous intake that declares a file field fails the
+ build and the publish gate** (`6ad226a`, #25737), under the rule
+ `public-form-file-field`. Fix (one line): take the file field off the public
+ form. A stored view keeps serving until its next save.
+- **Every anonymous write is refused a file value**, not only the public form
+ door (`b4199c9`, #26036). Fix: leave file fields empty in an anonymous write;
+ a signed-in caller provides the file through the authenticated upload doors.
+- **A record write claims or copies only a file its caller may read**
+ (`3761710`, #25951), under the download door's own rule. Fix: reference a
+ file as a caller who may read it, or upload it again; a server-side copy
+ between records runs as a system write.
+- **An unclaimed file is served only within its organization** under the
+ `isolated` and `group` postures (`3b8ea15`, #25704); anyone else gets
+ `403 FILE_DOWNLOAD_DENIED`. Fix: download it while acting in its
+ organization, have a record claim it, or mark it `acl: 'public_read'`.
+- **A record write naming a file its caller may not read is refused at the
+ field** with `400 VALIDATION_FAILED` and `invalid_value` (`7e4af20`, #26237);
+ so is an id that names no file of the organization. Fix: name a file the
+ caller may read, or leave the field empty.
+- **A file its record lets go keeps that record's read rule** until it is
+ claimed again or collected (`d9e6ac2`, #26144). `sys_file` gains
+ `released_ref_object` and `released_ref_id`, which schema sync adds. Fix:
+ read or claim such a file as a reader of that record.
+- **A seed record's file field holding an id that names no stored file is
+ refused, by row** (`2163e80`, #26244). Fix: replace the value with
+ `seedFile('')`, imported from `@objectstack/spec/data`,
+ or remove it. The same change adds `seedFile`: `os build` writes the bytes
+ into the artifact and the seed loader stores them.
+- **A flow screen field typed `file` collects an upload, and the resume
+ refuses any value that is not a file id** with `400 INVALID_SCREEN_INPUT`
+ (`6d9d3ef`, #26134). Fix: a caller that resumes the run itself uploads first
+ and posts the `sys_file` id; a field not meant for an upload takes another
+ `type`.
+
+#### Metadata the build and the save doors now refuse
+
+- **An approval node refuses `excludeSubmitter: true` beside
+ `onEmptyApprovers: 'auto_approve'`** (`4bcc4df`, #25700). Fix: choose
+ `'admin_rescue'` (the default), `'fail'`, or `'fallback'` with
+ `fallbackApprovers`. `excludeSubmitter` is new in this release, so no
+ released source holds the pair.
+- **An action's `confirmText`, `successMessage` or `errorMessage` finding is
+ an error, not a warning** (`0c81694`, #25702): a blank sentence, a hole over
+ a root the key does not bind, a single-brace `{x}` token, logic in a hole, or
+ a `${…}` token the runner shows raw. `os validate` and `os build` fail, and
+ an active save answers `422 INVALID_METADATA`. Fix: write the hole the
+ finding prescribes (`Delete {{ record.name }}?`), name a root the key binds,
+ double a single brace, move logic out of the hole, or delete a blank key.
+- **The holeless `${record.*}` `confirmText` and `${result.*}`
+ `successMessage` are refused too** (`d727348`, #25989): the Console's runner
+ shows them as written. Fix: `Delete ${record.name}?` becomes
+ `Delete {{ record.name }}?`, and `Invitation sent to ${result.email}` becomes
+ `Invitation sent to {{ result.email }}`.
+- **A code package's action is stored as its `ActionSchema` parse**
+ (`e44a1ff`, #25699), so a body the schema refuses fails registration with
+ `422 INVALID_METADATA`, and `requiresFeature` now takes effect on bound
+ actions. Fix: correct the action as `issues[]` describes; one that passes
+ `os validate` is unaffected.
+- **An object's own actions are parsed the same way** (`a4eae06`, #25981): a
+ raw manifest's object whose action fails `ActionSchema` is refused, and
+ nothing of it is stored. Fix: correct the action at `actions.N.KEY` as
+ `issues[]` describes.
+
+#### Flows and notifications
+
+- **A `notify` node mails the address in an email field of the trigger
+ record, and refuses a computed address no user holds** (`d1e329e`, #25721).
+ In a node whose `channels` include `email`, a recipient an expression
+ evaluates to an address must belong to a user, or the node fails. One
+ address receives at most 5 email-field sends per hour. Fix: give that
+ address its own node with `recipients: { emailField: 'FIELD' }` and
+ `channels: 'email'`, or give the person a user.
+ `@objectstack/service-automation` now depends on `@objectstack/plugin-auth`.
+- **A `notify` node that does not name `email` fails on an address no user
+ holds**, written out or computed, instead of reporting success (`b0384f3`,
+ #25964). Fix: add `email` to the node's `channels` to mail a written-out
+ address, or name a user (a user id, an audience selector such as `team:ID`,
+ or the address the user holds).
+- **The flow trigger doors refuse a `params` that is not a JSON object** with
+ `400 VALIDATION_FAILED` naming `params` (`e12d3f3`, #25974). Fix: send
+ `params` as an object (`{"params":{"ownerId":"u1"}}`), or leave the key out.
+- **The action door refuses a body or `params` that is not an object, and the
+ trigger door a body that is not one** (`3821ec8`, #26034). Fix: send each as
+ an object, for example `{"recordId":"…","params":{"note":"x"}}`, or leave it
+ out.
+
+#### REST, MCP, imports and analytics
+
+- **Eight doors answer an unknown resource `404 RESOURCE_NOT_FOUND`**
+ (`0dc4b5d`, #25628): `POST /api/v1/packages/:id/publish-drafts` and
+ `discard-drafts`, `GET /api/v1/packages/:id/commits`,
+ `GET /api/v1/automation/:name/runs`, `DELETE /api/v1/automation/:name`, and
+ `PATCH`, `DELETE` and `migrate-credential` on `/api/v1/datasources/:name`.
+ Fix: `packages.publishDrafts`, `discardDrafts`, `listCommits`,
+ `automation.listRuns` and `automation.delete` on `ObjectStackClient` now
+ reject for an unknown id; treat that as "no such resource".
+- **An inline import mapping entry's `transform`, `defaultValue` and
+ `required` are applied** (`50af6fa`, #25540), on `POST
+ /api/v1/data/:object/import`, the async job and the admin user import.
+ `transform: 'date_format'` is refused `400 UNSUPPORTED_TRANSFORM`, and a
+ blank required cell `400 MISSING_REQUIRED_FIELD`. Fix: drop `date_format`
+ (a date field still parses the cell), and fill a required column or give it
+ a `defaultValue`.
+- **An `update` / `upsert` import refuses its own rows that share a match
+ key**, before any write, with `DUPLICATE_MATCH_KEY` (`617f27e`, #26250).
+ Fix: keep one row per key and import again.
+- **`GET` and `DELETE` on `/api/v1/mcp` answer `405` with `Allow: POST`**
+ (`bb85f5f`, #25774). An MCP client stops re-opening the stream by itself.
+ Fix: a probe or health check that read `GET /api/v1/mcp` as `200` sends a
+ `POST` `initialize` instead.
+- **A NOT NULL refusal on a declared field answers `400 VALIDATION_FAILED` at
+ every door**, named by the field's label (`48e84a9`, #26026); the dispatcher
+ `/data` doors answered `500`. Fix: a TypeScript caller moves from
+ `notNullRefusalSite(answer)` to `notNullViolationSite(error)` in
+ `@objectstack/types`, and drops `relabelNotNullRefusal`; a client reads the
+ validator's envelope.
+- **A NOT NULL refusal on a column no field declares answers the same `400`
+ body at the dispatcher `/data` and actions doors** (`1af2cea`, #26171),
+ where they answered `500 INTERNAL_ERROR` and `400 VALIDATION_ERROR`. Fix: read
+ `error.code` and `error.details`; the driver's text no longer reaches the
+ wire. `@objectstack/types` adds `driverNotNullRefusalAnswer`.
+- **The analytics admission bridge answers `503 SERVICE_UNAVAILABLE` when the
+ `security` service is missing or cannot be used** (`2e6d446`, #25673), not
+ `403`. Fix: treat `503` as a server-side fault, and register a `security`
+ service (for example `plugin-security`) where analytics should answer.
+- **An analytics read gate that could not decide answers `503
+ SERVICE_UNAVAILABLE`** (`f1d6be0`, #25595): a provider that threw, field
+ permissions or the API exposure declaration that could not be resolved.
+ Real denials keep `403`. Fix: retry once the resolver is healthy, and do not
+ show it as a missing grant.
+- **A filtered measure asked as `CUBE.MEASURE` applies its filter**
+ (`3117289`, #26007), as the bare name did, and the read gate judges the
+ filter's fields. Fix: nothing for a caller that wanted the measure as
+ declared; one that relied on the unfiltered total asks an unfiltered
+ measure.
+- **A `driver-sql` `upsert` with nothing left to merge is a plain `INSERT`**
+ (`88f22e0`, #26015), so a collision raises the unique-violation error instead
+ of merging every column. Fix: catch the unique violation, or use `update()`
+ to change a stored row.
+
+#### Packages and installs
+
+- **An install checks the package's declared `dependencies` first**
+ (`3446d43`, #25986), at `POST /api/v1/packages`, the in-process
+ `installPackage` and `POST /api/v1/marketplace/install-local`; an unmet
+ entry refuses the whole install `422 DEPENDENCY_MISSING`, naming each one.
+ Fix: install the dependencies, at versions inside the declared ranges,
+ first.
+- **A package that declares an installed app in `manifest.dependencies` may
+ share its namespace** (`be78977`, #26054), and a boot hydrates installed
+ packages in dependency order. A multi-package artifact's wrapper id no longer
+ counts as delivered, and `DeclaredDependencyCheck.deliveredIds` is removed.
+ Fix: a module that uses its app's namespace declares the app in
+ `manifest.dependencies`.
+
+#### Deployment
+
+- **`kind: 'react'` pages are off unless the deployment turns them on**
+ (`5e7cf63`, #25789). With the tier off, a react page shows a notice and the
+ Console's policy drops `'unsafe-eval'`. Fix: set
+ `OS_PAGE_REACT_ENABLED=true` on a deployment that serves react pages;
+ `OS_PAGE_REACT=on` is still read, with a deprecation warning. Stored pages
+ need no change.
+
+#### Code against the published `@objectstack/*` packages
+
+| Removed or changed | Use instead |
+| :--- | :--- |
+| `renderTemplate` from `@objectstack/plugin-email` (`92a0073`, #26399) | an `email_template` item, rendered by `services.email` through `renderTemplate({ template, data, locale })` or `sendTemplate` |
+| `ResolveLocalizationInput.tenantId` of `resolveLocalizationContext` in `@objectstack/core` (`65145d3`, #26078) | `organizationId`, the organization's id; never an environment id. A spread object that still carries `tenantId` is ignored, so search for it |
+| `@objectstack/sdui-parser`'s `validateTree` judges the nodes in a manifest entry's `slots` (`8faba89`, #26211) | nothing for the save gate's manifest, which carries no `slots`. `SchemaNode` and `ValidationResult` are deprecated aliases of `ParsedSchemaNode` and `ManifestValidationResult` |
+| a template's formatter locale read from a root named `locale` (`14b5597`, #26282) | the evaluate context's `locale`: `templateEngine.evaluate(expr, { extra: roots, locale })`, or `fillTemplateHoles(source, roots, locale)` |
+
+#### Console and the `@object-ui/*` packages
+
+- **objectui's own breaking changes from its 18.1.0 to the import** arrive as
+ one entry (`9a0af4e`, #26068). Five retirements, each with its migration in
+ `ui/.changeset/`: the raw `html` node (`13449-retire-html-renderer.md`), the
+ `module` widget source (`13450-retire-widget-module-source.md`), nine
+ zero-consumer modules with `@object-ui/runner` made private (the four
+ `13453-*` files on modules, hooks, `LiveCursors` and test helpers), the
+ `@object-ui/providers` trio (`13453-providers-retire-trio.md`), and nine
+ duration keys that now end in `Ms` or `Seconds`
+ (`25762-duration-keys-carry-unit.md`). Also: the `react` / `react-dom` peer is
+ `^19.0.0`, so a React 18 host is no longer supported, and the `react-pages`
+ capability defaults off in `@object-ui/core`. Fix: follow each file's table,
+ and move a host to React 19.
+- **The renderer runs `@objectstack/sdui-parser`, and `@object-ui/sdui-parser`
+ is deleted** (`80f267d`, #26343). An html-tier page whose attribute does not
+ fit its declared type now shows the compile-error block instead of dropping
+ the value. Fix: depend on `@objectstack/sdui-parser`, and write the value in
+ its declared type (`columns={['name','amount']}`).
+- **`useAdapter()` and `AdapterCtx` from `@object-ui/react` are typed by
+ `DataSource`** (`ef025fe`, #26292), and the package no longer depends on
+ `@object-ui/data-objectstack`. Fix: declare your adapter once with a
+ `declare module '@object-ui/react'` augmentation of
+ `AdapterContextRegister` (`adapter: ObjectStackAdapter`), or narrow where you
+ read an ObjectStack-only member. A host that imports `@object-ui/app-shell`
+ keeps today's type.
+- **54 root exports of `@object-ui/app-shell` are retired** (`aec5f7b`,
+ #26219). Fix: derive a retired type from the name the entry still exports
+ (`UseUrlOverlayOptions` becomes `Parameters[1]`), as
+ the CHANGELOG's FROM → TO list gives for each; the rest have no
+ replacement.
+- **The node-level `visibleOn`, `hiddenOn` and `disabledOn` are retired**
+ (`e843b5b`, #26274), with no alias window. Fix: rename the key and keep the
+ value: `visibleWhen`, `hidden`, `disabled`. A form field's
+ `fields[].visibleOn` is a different slot and is unchanged.
+- **The Console stops offering New, Edit and Delete when `/me/permissions`
+ omits the bit** (`b727956`, #26273), because the server refuses them. Fix:
+ nothing, unless a user should have those actions: grant the bit in a
+ permission set.
+- **`FilterBuilderRow.value` is the spec rule's value: optional, and may be
+ `null`** (`0489214`, #26338). Fix: code that reads `row.value` handles
+ `undefined` and `null` first.
+- **A `FilterBuilderRow` list value holds strings and numbers only**
+ (`57ab06b`, #26380). Fix: write a boolean entry as its string spelling, or
+ keep the boolean as a scalar under `equals`.
+
+### 自动迁移 — converted for you by `os migrate meta` [#migrated-for-you]
+
+Seven breaking changes carry an ADR-0087 registration on the protocol-18 step, 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.
+
+| Change | ADR-0087 id | What `os migrate meta` does, and what is left to you |
+| :--- | :--- | :--- |
+| A permission set can no longer declare `allowDelete: false` beside `modifyAllRecords: true`; an omitted `allowDelete` beside it now parses to `true` (`f6ba134`, #25707) | `permission-modify-all-allow-delete-false-to-true`, `permission-modify-all-allow-delete-false-refused` | converts a stored row or built artifact to `allowDelete: true`, which changes no access decision. A source that declares the pair is refused: delete `allowDelete`, or, for "edit every record, delete none", drop `modifyAllRecords` and add a `criteria` sharing rule with `accessLevel: 'edit'` (`os explain security-modify-all-delete-withheld`) |
+| An SLA escalation's `escalateTo` naming a `team:`, `department:` or `org_membership_level:` address, or the prefix ADR-0090 D3 retired, is refused (`9993165`, #26154) | `approval-escalation-dead-target-refused` | lists each one; no conversion, because only you know who was meant. Write a position name, `position:NAME`, a user id or an email; the retired prefix becomes `position:` |
+| The package upgrade model leaves `@objectstack/spec`: `UpgradePlanSchema` and its family, `IPackageService.planUpgrade` / `upgrade` / `rollback` / `getSnapshot`, and `PackageUpgradeResponse.data.plan` (`557973d`, #25757) | `kernel-package-upgrade-family-retired` | lists it; nothing ever read or produced these shapes. Delete the imports. A new version over an installed package is `POST /api/v1/packages` with `overwrite: true`; the package rollback served is `POST /api/v1/packages/:id/rollback` with a `commitId` |
+| `PackageDependency.optional` and `AppManifest.dependencies` are tombstones (`436d284`, #26401) | `dependency-unread-spellings-retired` | lists it; nothing read either key. Delete the key, and declare each dependency with its range in `manifest.dependencies` |
+| A locale must be a BCP-47 tag: `zh_CN`, a padded value or a language name is refused (`39a78b1`, #25983) | `translation-locale-tag-canonicalized`, `translation-locale-non-tag-refused` | rewrites `zh_CN` to `zh-CN` and trims whitespace, in stored rows and with `--from 17 --write` in sources; any other value is reported as a TODO. Replace a language name with its tag |
+| A `runAs: 'system'` hook or a `type: 'script'` action whose sandboxed JS body writes `sys_user_position`, `sys_user_permission_set`, `sys_invitation` or `sys_member` is refused at save, not only when it runs (`07586f5`, #26430) | `hook-action-body-system-identity-assignment-write-refused` | lists each one; no conversion, because the route is your choice. Set the hook's `runAs` to `'user'`, or move an action's grant into a `runAs: 'user'` flow; use the organization endpoints for invitations and memberships |
+| `PositionGrantVia` loses `'name'`, and Setup → Positions drops its "(by name)" badge (`9494dd8`, #26398) | `position-grant-via-name-retired` | lists it; no server serves the value. Delete a branch that compares with `'name'`, and name the set in the position's `permissionSets` |
+
+### 新增能力 — new capabilities [#new-capabilities]
+
+#### Access and approvals
+
+- **A field permission entry can carry a row condition, `when`, on its edit
+ vote** (`c8cb510`, #25980), in the CEL of `rowLevelSecurity[].using`
+ (`status == 'approved'`), and the Studio permission-set form offers it as
+ **When (edit)** (`7111e14`, #26010).
+- **`sharing.controlledByParent.ownerMayEdit: true`** lets the owner of a
+ `controlled_by_parent` detail row update it with read access to the master
+ (`5e113b3`, #26005).
+- **A flow declares who may start it** with `requiredPermissions`, checked at
+ the trigger route, a `type: 'flow'` action and a flow endpoint (`f1ddace`,
+ #25643).
+- **An approval step's `excludeSubmitter`** keeps the submitter off the slate
+ its approvers resolve to (`49653f9`, #25632); the engine honours it in this
+ release (see other fixes).
+- **A record-anchored approval lock read** answers `{ locked }` to every
+ caller who can read the record, at
+ `GET /api/v1/approvals/records/:object/:recordId/lock` (`6a2866a`, #26361).
+- **An administrator who is not the approver sees one status line and one
+ override menu** on a record under approval (`054652b`, #26053).
+- **A delegated admin's out-of-scope save shows the server's reason** in the
+ Console (`0e7be7e`, #26363).
+- **A member takes back their own comment reaction** through
+ `DELETE /api/v1/comments/reactions/:id`, with no object delete grant
+ (`1a298dd`, #26345).
+- **A free-text `search` resolves only over the fields the caller may
+ query** (`5874d25`, #25705), so a user or owner picker no longer fails
+ `403` for a non-admin member.
+
+#### Organizations, sign-in and demo data
+
+- **A single-organization deployment can rename its organization**
+ (`a0fac02`, #25661), and `PUBLIC_AUTH_FEATURES` books Rename Organization
+ under the organization capability (`a0fac02`, #25661).
+- **The platform OIDC provider emits the standard `locale` claim** at
+ userinfo (`431f065`, #26012).
+- **An invitee who opens the link signed out is greeted for the invitation**
+ and offered "Create an account and join" first (`326a559`, #26158).
+- **`demoStaff`** declares the demo accounts a development boot creates and
+ the seed deals its rows to (`f00e8d5`, #25830).
+
+#### Flows and actions
+
+- **A `notify` recipient can be the address in an email field of the trigger
+ record**, `{ emailField: 'applicant_email' }` (`6021b5e`, #25645).
+- **An `api` flow's start node may name its inbound hook secret,
+ `config.secretRef`**, instead of carrying it (`fa44dd2`, #25703).
+- **A `type: 'flow'` action may declare `onSuccess`**, and the Console
+ navigates after a run that completes (`dc71845`, #26011).
+- **A read-only `record_link` screen field** shows a record the run created,
+ as a link to its page (`4548ba3`, #26355).
+- **A hook body reads `ctx.provenance`**, so a guard that tells a flow's write
+ from a user's ships as metadata (`1ee1d1b`, #25952).
+- **An action's `confirmText`, `successMessage` and `errorMessage` are
+ `template` slots** over the roots the Console's runner binds (`384bb7b`,
+ #25637). The refusals that follow from it are under
+ [需手动处理](#action-required).
+
+#### Data, imports, seeds and analytics
+
+- **An import column can be written only when its row is created
+ (`applyOn`), and `matchFields` takes ordered key sets** (`b77bb37`,
+ #25664).
+- **A seed dataset column can be written only on create**,
+ `columns.FIELD.applyOn: 'create'` (`493a6c4`, #25813).
+- **A package's action can launch a flow, and its widget bind a dataset, that
+ another package of the same artifact ships**, through
+ `defineStack(config, { artifactFlows })` (`cc10e74`, #25617).
+- **A dataset measure can aggregate the time between two datetime fields**,
+ `duration: { from, to, unit }` (`d1663ba`, #25903).
+- **A filter can compare a datetime column against now shifted by days held
+ on the record**, `{ $now: true, addDays }` (`be531f6`, #26261).
+- **A list-view grouping level can order its groups by another field**,
+ `grouping.fields[].orderBy` (`b00fc55`, #25828).
+- **Object-level `composite` / `repeater` are documented as untyped JSON**,
+ and a typed list points to a child object (`4034f47`, #25755).
+
+#### Translations
+
+- **A form section entry's `label`, `placeholder` and `helpText`** are
+ translatable at `objects.