diff --git a/content/docs/protocol/backward-compatibility.mdx b/content/docs/protocol/backward-compatibility.mdx index 49f1ab46dca..559f9049dd8 100644 --- a/content/docs/protocol/backward-compatibility.mdx +++ b/content/docs/protocol/backward-compatibility.mdx @@ -19,7 +19,7 @@ ObjectStack follows strict backward compatibility guarantees to ensure predictab **The v18 line has exited prerelease mode.** While Changesets was in pre mode with the tag `next`, every release cut from `main` was the next `18.0.0-next.N` on npm's `next` tag, and `scripts/check-changeset-no-major.mjs` stood aside in pre mode, so a pull request could declare a major bump. Changesets has exited pre mode, so the line's GA version is `18.0.0`, which publishes to npm's `latest` tag, and the guard again fails a pull request that declares a `major` bump. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line). -**After 18.0.0, a breaking change ships in a MINOR or a PATCH release.** The maintainer's 2026-10-21 ruling ([#25105](https://github.com/objectstack-ai/objectstack/issues/25105)) keeps the no-major guard and grades each change by the metadata protocol: a key, value or metadata type added, narrowed or given a new meaning takes MINOR, and every other change takes PATCH, breaking or not. A non-protocol breaking change (a REST response, a boot refusal, a CLI exit status, a removed runtime export) keeps its `**BREAKING**` banner and ships as a PATCH. 18.1.0, the first release under the rule, is a MINOR that carries 90 changesets declared breaking. +**After 18.0.0, a breaking change ships in a MINOR or a PATCH release.** The maintainer's 2026-10-21 ruling ([#25105](https://github.com/objectstack-ai/objectstack/issues/25105)) keeps the no-major guard and grades each change by the metadata protocol: a key, value or metadata type added, narrowed or given a new meaning takes MINOR, and every other change takes PATCH, breaking or not. A non-protocol breaking change (a REST response, a boot refusal, a CLI exit status, a removed runtime export) keeps its `**BREAKING**` banner and ships as a PATCH. 18.1.0, the first release under the rule, is a MINOR that carries 91 changesets declared breaking. ObjectStack follows [Semantic Versioning 2.0.0](https://semver.org/) (`MAJOR.MINOR.PATCH`). The table below describes what each component means; which version a breaking change ships in depends on the line, as the callout above says. diff --git a/content/docs/releases/index.mdx b/content/docs/releases/index.mdx index 97b94861c06..6b146cab9d2 100644 --- a/content/docs/releases/index.mdx +++ b/content/docs/releases/index.mdx @@ -72,7 +72,7 @@ meaning takes `minor`, and every other change takes `patch` — a REST door, a driver, a boot refusal, the CLI or the Console — breaking or not. A non-protocol breaking change keeps its `**BREAKING**` banner and its ADR-0087 disposition. 18.1.0 is the first release cut under the rule: a minor carrying -90 changesets declared breaking, 42 of them graded `patch`. +91 changesets declared breaking, 43 of them graded `patch`. What that means when you read a version number: diff --git a/content/docs/releases/v18/18-1.mdx b/content/docs/releases/v18/18-1.mdx index 25981ed6264..e3ece7352a8 100644 --- a/content/docs/releases/v18/18-1.mdx +++ b/content/docs/releases/v18/18-1.mdx @@ -3,24 +3,21 @@ title: 18.1.0 description: "Release notes and upgrade checklist for 18.1.0, the first minor of the v18 line: a minor that carries breaking changes. ADR-0131 finishes (an organization-less row confers nothing), a production os serve with no organization object is refused, nine metadata narrowings with automatic conversions, one federation rule at every datasource door, and an unreadable SQLite file is refused instead of replaced." --- - -**How this page was compiled.** From the 18.1.0 changesets on `main` at -`ba9e0c3749` (2026-10-24), before the 18.1.0 cut: a rehearsed -`pnpm run version` in a throwaway clone at `b2c0752900` (73 packages move to -18.1.0, and their `## 18.1.0` sections held 227 distinct entries), and the 31 -changesets merged after it, graded the same way. That makes **258 distinct -entries**. The five sections below use the grouping the GitHub Release body -uses, and the counts are that grouping's: **81** need you to act, **9** are -converted for you, **47** add capabilities, **3** declare a semantic change, -and **118** are other fixes. Nothing here was -read off a published 18.1.0 package or walked on an upgrade yet: every refusal -is described as its changeset describes it. Anything that lands on `main` before -the cut is in the CHANGELOG and not on this page. + +**How this page was compiled.** From the `## 18.1.0` sections of the published +CHANGELOGs (version commit `e649c2d91e`, #25606): 73 packages carry one, and +together they hold **265 distinct entries**. The five sections below use the +grouping the GitHub Release body uses, and the counts are that grouping's: +**82** need you to act, **9** are converted for you, **50** add capabilities, +**2** declare a semantic change, and **122** are other fixes. The +[upgrade checklist](#upgrade-checklist) was walked on 2026-10-24 from 18.0.0; +each line it measured says so. A refusal it did not reach is described as its +changeset describes it. ## Highlights — 18.1.0 -- ⚠️ **A minor that breaks.** 18.1.0 is a minor version, and 90 of its +- ⚠️ **A minor that breaks.** 18.1.0 is a minor version, and 91 of its changesets are declared breaking. Under the level rule the maintainer ruled on 2026-10-21 (#25105, "ruling A"), the v18 line takes no further major: a change to the metadata protocol takes `minor`, and every other change takes @@ -72,6 +69,41 @@ the cut is in the CHANGELOG and not on this page. --- +## Release facts, as measured + +Read with `npm view` and `npm pack` on 2026-10-24, after the publish: + +| | Measured | +| :--- | :--- | +| `latest` | `18.1.0`, on every `@objectstack/*` package read (`spec`, `cli`, `runtime`, `core`, `objectql`, `skills`, `organizations`, `console`) and on `create-objectstack` | +| `next` | `18.0.0-next.5`, still **older** than `latest`. ⛔ Never install `@next` for 18 | +| `rc` | `17.0.0-rc.6` (`@objectstack/skills` and `@objectstack/organizations` carry no `rc` tag) | +| Published | `@objectstack/cli@18.1.0` at 2026-10-24 09:06 UTC; 73 GitHub Releases, one per package, tagged `@18.1.0`, between 09:12 and 09:13 UTC | +| Version commit | `e649c2d91e` (#25606). The first 18.1.0 version commit, `74fa3cb6df` (#24876), was reverted before anything published (#25605) | + +Read the 18.1.0 CHANGELOGs with two cautions: + +- **The commit beside an entry is mostly not its own.** 260 of the 265 entries + open with `3937697`, the commit of #25605 that reverted the first cut and + re-added every pending changeset; only the five changesets merged after it + carry their own commit. The same is true in the published tarballs: all 34 + entries of `@objectstack/cli@18.1.0`'s `## 18.1.0` section read `3937697`. + To find the change behind an entry, search its headline in the repository's + history; this page cites each entry's own commit. +- **A pinned `@objectstack/cli` still ranges.** The published CLI declares + eight of its `@objectstack/*` dependencies as caret ranges: `core`, + `driver-memory`, `driver-mongodb`, `driver-sql`, `driver-sqlite-wasm`, + `objectql`, `observability` and `runtime` (`^18.1.0` in 18.1.0, `^18.0.0` in + 18.0.0). An app that pins every `@objectstack/*` package exactly therefore + gets a newer 18.x of those, and of the packages they depend on, as soon as one + publishes. Measured: an app pinned to exactly `18.0.0` installed today holds + 17 `@objectstack/*` packages at 18.1.0 beside 60 at 18.0.0. See + [Move the pins](#1-move-the-pins) for the override that holds them. + +The Docker image was not read for this page. + +--- + ## What's new in 18.1.0 ### The version number is not the signal @@ -88,7 +120,7 @@ else: | Everything else: REST, drivers, services, the CLI, the Console, `feat:` and `fix!:` alike | `patch` | So a **patch** release can break a REST client, a boot or a deployment -script, and 18.1.0 carries 42 changesets graded `patch` that say **BREAKING**, +script, and 18.1.0 carries 43 changesets graded `patch` that say **BREAKING**, beside 48 graded `minor` ones. Pin exact `@objectstack/*` versions, and read the release notes for every version you cross. Each entry in a CHANGELOG states its kind on its own `Metadata-protocol:` line, and the GitHub Release body groups the @@ -98,11 +130,13 @@ From 18.1.0 the scaffolds follow the same advice: `npm create objectstack`, `os init` and `os create` write the exact `@objectstack/*` version (`18.1.0`, not `^18.1.0`), and `--in-repo` still writes `workspace:*` (`975d5b3`, #25539). A project scaffolded earlier keeps its caret ranges until -you change them. +you change them. Measured: `create-objectstack@18.1.0` wrote `"18.1.0"` for all +nine `@objectstack/*` entries, and `create-objectstack@18.0.0`, run on +2026-10-24, wrote `^18.0.0` and installed 18.1.0. ### Breaking changes you must act on -The 81 entries that are breaking and have no ADR-0087 conversion, and one boot +The 82 entries that are breaking and have no ADR-0087 conversion, and one boot refusal (`db1c7cc`) that its changeset declares as an addition. Each line names the one fix; the CHANGELOG entry carries the full FROM → TO. @@ -231,7 +265,11 @@ door (`422 INVALID_METADATA`), where 18.0.0 accepted it. params, a single-brace `{x}`, logic or an unknown formatter inside a hole, or a blank `''`. 18.0.0 warned. Fix: write holes over `count` or the params, double a literal single brace, and delete a blank `confirmText`. A view - already stored still loads. + already stored still loads. *Probed on 18.1.0:* a view saved with + `confirmText: 'Retitle {x} notes?'` answered `422 INVALID_METADATA` with + `` A bulk action's `confirmText` reads `{{ }}` template holes (ADR-0032 §3), + not the single-brace `{…}` dialect `` and named `count` and the param as the + holes it binds; `'Retitle {{ count }} notes?'` saved. - **A row policy that reads `x in current_user.KEY` which nothing owns, at the permission save door** (`b531016`, #25550). It answered 200 and matched no rows; it is now `422 INVALID_METADATA`. An object write is now judged by the @@ -310,10 +348,20 @@ door (`422 INVALID_METADATA`), where 18.0.0 accepted it. refused by every data verb (`8010b64`, `72fad92`, `a1e51d6`, `fb7501b`, `882372d`). Fix: nothing if the declarations say what you mean; otherwise set `enable.apiEnabled`, or re-enable the package. +- **Reassigning an approval to an address that resolves to no one is + refused** (`a57643d`, #25592): `POST /api/v1/approvals/requests/:id/reassign` + and `ApprovalService.reassign` answer `400 VALIDATION_FAILED` naming the + address, where a `to` nobody holds was written onto the slate and the request + waited on no one. A user id or email must belong to a member of the request's + organization, and `position:` must have a holder there; `team:`, + `department:` and other addresses are always refused. Fix: reassign to a + person in the organization, or to a held position. - **`POST /api/v1/meta/:type/:name/rollback` takes `toVersion` as a JSON whole number of at least 1** (`0fab7ba`, #25532), and anything else answers `400 INVALID_REQUEST`. The query form `?toVersion=3` still works. Fix: send - `{"toVersion": 2}`, not `"2"` or `2.5`. + `{"toVersion": 2}`, not `"2"` or `2.5`. *Measured in the walk:* `{"toVersion":"1"}` + and `{"toVersion":1.5}` answered `400` with `'toVersion' (positive integer) is + required`, and `{"toVersion":1}` rolled the view back. - **The Data API refuses a malformed or negative page window with `400`** naming the parameter (`17f3c65`, #25260), and the published list-query schemas declare it a non-negative integer (`5f30f00`, #25300). Fix: send a @@ -498,6 +546,14 @@ rows. `controlled_by_parent` detail follows its master. Nothing changes for a principal that does not hold the `external` grade, and no earlier release shipped it. +- **A share or sharing rule on a public object reaches an external member** + (`4a25192`, #25591). On a `public_read_write` object, or a system object + with no `sharingModel`, `SharingService.grant` now admits a `user` recipient + who is an `external` member of the record's organization, and a sharing + rule writes rows for the external members it expands to and skips the + internal ones. Every other recipient keeps `422 SHARING_NOT_ENABLED`. The + lint's `sharing-rule-object-not-shareable` on a public object is now a + warning. - **`FlowSchema.runAs` now says what a `system` run may not write** (`025f6de`, #25564): its description names the refused writes to the four identity-assignment objects (above, under security), and ADR-0087 D3 records @@ -505,7 +561,10 @@ rows. so `os migrate meta` lists it for a flow that wrote those rows. It carries no conversion, and every flow parses as before. -A change of meaning that also breaks is listed with the breaking changes above. +#25541 and #25591 declare `Metadata-protocol: semantic` too; the GitHub Release +body files them under new capabilities, because each widens what a door +admits, and the counts above follow it. A change of meaning that also breaks +is listed with the breaking changes above. ### Notable fixes in 18.1.0 @@ -540,7 +599,9 @@ A change of meaning that also breaks is listed with the breaking changes above. #25370), and the scaffolds ship a starter test so a new project's `npm test` passes (`6bc8cc6`, #25236; `9c431ba`, #25196). Saving the mail provider "None (log only)" stops delivery, where the SMTP transport the boot built kept - sending (`0ec4c5a`, #25497). + sending (`0ec4c5a`, #25497). `POST /api/v1/data/:object/import` reads a + dotted (`2011.07.01`) or compact (`20090615`) year-first day, and matches a + select cell's option value in any case (`9f39094`, #25596). The other fixes are in the per-package CHANGELOGs. @@ -599,26 +660,74 @@ also requires. ### 18.1.0 -**Not walked yet.** This list is compiled from the changesets before the cut. -No app has been moved from 18.0.0 to 18.1.0 for it, and no refusal below was -read off a published package. +**How far this list has been walked.** On 2026-10-24, from 18.0.0, once: an app +scaffolded with `create-objectstack@18.0.0` (the `blank` template, pnpm, no +skills), pinned to exactly `18.0.0` with the override in step 1, was booted on +18.0.0 with `objectstack start` and `OS_AUTH_SECRET` set. It created a SQLite +database holding one administrator, signed up through the auth API, and three +notes. It was then moved to 18.1.0 from npm, and each step below was run. Each +line says how it was measured: + +- **Measured in the walk** — the step was run, and the quoted text is what + 18.1.0 printed. +- **Probed on 18.1.0** — the walk's app carries none of it, so a probe was sent + to the running 18.1.0 server, and the quoted text is its answer. + +⛔ The walk was one small app on SQLite, under the `single` posture. Walled +postures, Postgres, MySQL, Turso, a multi-environment host, an embedded kernel, +a flow, a webhook and the skills sync were not walked, and no upgrade of a +production application was run for this page. A line with no mark is compiled +from the changesets. #### 1. Move the pins -Move every `@objectstack/*` dependency to `18.1.0` together, and -`@objectstack/skills` with them, as in the -[18.0.0 walk](/docs/releases/v18/18-0#1-move-the-pins). Pin the exact version: -a caret range takes every later 18.x by itself, and a patch can break a -non-protocol surface. A project `npm create objectstack` scaffolded before -18.1.0 carries caret ranges: replace each `^18.x.y` with `18.1.0` and -reinstall. +- **Move every `@objectstack/*` dependency to `18.1.0` together**, and + `@objectstack/skills` with them, as in the + [18.0.0 walk](/docs/releases/v18/18-0#1-move-the-pins). Pin the exact + version: a caret range takes every later 18.x by itself, and a patch can + break a non-protocol surface. A project `npm create objectstack` scaffolded + before 18.1.0 carries caret ranges: replace each `^18.x.y` with `18.1.0` and + reinstall. The walk moved nine exact `18.0.0` pins to `18.1.0`, and + `pnpm install` left no `@objectstack/*` package at 18.0.0 in the lockfile and + printed no peer-dependency warning. *Measured in the walk.* +- **Hold the CLI's own ranges with an override.** An exact pin does not hold + the eight packages `@objectstack/cli` ranges (see + [Release facts](#release-facts-as-measured)). With pnpm, add this to + `pnpm-workspace.yaml`, at the version you pin: + + ```yaml + overrides: + '@objectstack/core': '18.1.0' + '@objectstack/driver-memory': '18.1.0' + '@objectstack/driver-mongodb': '18.1.0' + '@objectstack/driver-sql': '18.1.0' + '@objectstack/driver-sqlite-wasm': '18.1.0' + '@objectstack/objectql': '18.1.0' + '@objectstack/observability': '18.1.0' + '@objectstack/runtime': '18.1.0' + ``` + + On the walk's app pinned to `18.0.0`, those eight lines (at `'18.0.0'`) took + the tree from 17 packages at 18.1.0 to none. pnpm 10.28 ignored the same + block under `"pnpm": { "overrides": … }` in `package.json` while + `pnpm-workspace.yaml` exists. npm's `overrides` was not tried. Move the + override with the pins at every upgrade. *Measured in the walk.* #### 2. Before the first boot -- Production `os serve` or `os start`: set `OS_AUTH_SECRET`, and do not rely on +- **Production `os serve`: set `OS_AUTH_SECRET`**, and do not rely on `--no-server` or `OS_MIGRATE_AND_EXIT=1` to boot without it. A kernel you embed needs it too, unless it boots with `NODE_ENV=development` or `test`. + Without it, `os serve` exited `1` with + `✗ This stack enables auth, but no auth secret is set, so AuthPlugin was not + composed and no caller can authenticate` and the line to set it. + *Measured in the walk.* +- **`os start` does not refuse: it mints a secret.** With no secret set, it + wrote a new one to `.objectstack/auth-secret` and booted. Boot with the + secret 18.0.0 used: with the same `OS_AUTH_SECRET`, the session signed in on + 18.0.0 was still valid on 18.1.0; a new secret ends every session. + *Measured in the walk.* - A stack that mounts no auth (`auth` tier off, `--preset minimal`) fails a production boot: enable auth. - CORS: list every cross-origin front-end in `OS_CORS_ORIGIN`, loopback origins @@ -636,6 +745,13 @@ os build os migrate meta --from 17 # the step-18 conversions, listed for your sources ``` +On the walk's app, `os validate` and `os build` passed on 18.1.0 with no +edit. `os migrate meta --from 17` exited `0` and printed +`417 manual change(s) require your judgment` (18.0.0 printed 406): read it as a +list to search, as on 18.0.0. `--from 18` replays nothing, and says so +(`No migration step exists for protocol 18 → 18`), so keep `--from 17`. +*Measured in the walk.* + Fix each refusal with the line in [Breaking changes you must act on](#breaking-changes-you-must-act-on) and [Converted for you](#converted-for-you-by-os-migrate-meta). The ones an app is @@ -647,11 +763,21 @@ managed datasource. #### 4. After the boot +- The data is as 18.0.0 left it. The walk's three notes read back, and the + administrator signed in. A clean boot printed no upgrade report at the + default log level. *Measured in the walk.* - Run `os migrate meta --stored` to list stored rows the step-18 conversions - rewrite. + rewrite. It takes no `--from` (`--stored=true cannot also be provided when + using --from`); on the walk's app, which had stored no metadata, it printed + `Examined 0 stored metadata row(s)` and a warning that an empty run attests + nothing. *Measured in the walk.* - Run `os migrate plan`. On a database 18.0.0 created, it lists the tenant columns still nullable; the ownership ceremony tightens them to `NOT NULL` - when you choose to run it. + when you choose to run it. On the walk's database it printed + `33 tenant column(s) are still nullable (ADR-0131 D1)`, said the ceremony + tightens 31 of them, and ended + `✓ Physical schema is in sync with metadata — nothing to migrate.` + *Measured in the walk.* - If the boot reports a notification preference collision, resolve it with `os migrate duplicates` and restart. - Check that REST and MCP callers do not depend on a disabled package's actions diff --git a/content/docs/releases/v18/index.mdx b/content/docs/releases/v18/index.mdx index a5d2a8937ef..cb5681fd86a 100644 --- a/content/docs/releases/v18/index.mdx +++ b/content/docs/releases/v18/index.mdx @@ -135,11 +135,11 @@ Each release below is a self-contained page: what it changed, what breaks, and its own upgrade checklist. - **[18.1.0](/docs/releases/v18/18-1)** — the first minor, and a minor that - breaks: 90 of its changesets are declared breaking (ruling A grades a + breaks: 91 of its changesets are declared breaking (ruling A grades a non-protocol breaking change `patch` and a protocol narrowing `minor`). Its [upgrade checklist](/docs/releases/v18/18-1#upgrade-checklist) moves an - 18.0.0 app to 18.1.0; it was compiled from the changesets before the cut and - has not been walked yet. + 18.0.0 app to 18.1.0, and was walked on 2026-10-24 from 18.0.0 on one small + SQLite app. - **[18.0.0](/docs/releases/v18/18-0)** — the GA cut. Its [upgrade checklist](/docs/releases/v18/18-0#upgrade-checklist) moves a 17.x app and its database to 18.0.0, and was walked from 17.7.0.