Skip to content

Commit b017346

Browse files
committed
docs(releases): 18.1.0 release facts as measured, and the upgrade checklist walked from 18.0.0
After the publish: npm tags, the version commit, the CHANGELOG counts read off the published sections (265 entries, 82/9/50/2/122, 91 declared breaking), two cautions (260 entries cite the revert commit 3937697; @objectstack/cli ranges eight @objectstack/* dependencies, so an exact pin still floats), and the walk of a create-objectstack@18.0.0 app to 18.1.0 on SQLite: measured pins and override, the os serve refusal and os start's minted secret, validate, build, migrate meta, migrate plan, and probes of the confirmText and rollback refusals. Folds in #25591, #25592 and #25596, the last changesets before the cut. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At7xrR4rgknXtej5ZkMJHd
1 parent e649c2d commit b017346

4 files changed

Lines changed: 165 additions & 39 deletions

File tree

‎content/docs/protocol/backward-compatibility.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ ObjectStack follows strict backward compatibility guarantees to ensure predictab
1919

2020
**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).
2121

22-
**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.
22+
**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.
2323
</Callout>
2424

2525
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.

‎content/docs/releases/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ meaning takes `minor`, and every other change takes `patch` — a REST door, a
7272
driver, a boot refusal, the CLI or the Console — breaking or not. A
7373
non-protocol breaking change keeps its `**BREAKING**` banner and its ADR-0087
7474
disposition. 18.1.0 is the first release cut under the rule: a minor carrying
75-
90 changesets declared breaking, 42 of them graded `patch`.
75+
91 changesets declared breaking, 43 of them graded `patch`.
7676

7777
What that means when you read a version number:
7878

‎content/docs/releases/v18/18-1.mdx‎

Lines changed: 160 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,21 @@ title: 18.1.0
33
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."
44
---
55

6-
<Callout type="warn">
7-
**How this page was compiled.** From the 18.1.0 changesets on `main` at
8-
`ba9e0c3749` (2026-10-24), before the 18.1.0 cut: a rehearsed
9-
`pnpm run version` in a throwaway clone at `b2c0752900` (73 packages move to
10-
18.1.0, and their `## 18.1.0` sections held 227 distinct entries), and the 31
11-
changesets merged after it, graded the same way. That makes **258 distinct
12-
entries**. The five sections below use the grouping the GitHub Release body
13-
uses, and the counts are that grouping's: **81** need you to act, **9** are
14-
converted for you, **47** add capabilities, **3** declare a semantic change,
15-
and **118** are other fixes. Nothing here was
16-
read off a published 18.1.0 package or walked on an upgrade yet: every refusal
17-
is described as its changeset describes it. Anything that lands on `main` before
18-
the cut is in the CHANGELOG and not on this page.
6+
<Callout type="info">
7+
**How this page was compiled.** From the `## 18.1.0` sections of the published
8+
CHANGELOGs (version commit `e649c2d91e`, #25606): 73 packages carry one, and
9+
together they hold **265 distinct entries**. The five sections below use the
10+
grouping the GitHub Release body uses, and the counts are that grouping's:
11+
**82** need you to act, **9** are converted for you, **50** add capabilities,
12+
**2** declare a semantic change, and **122** are other fixes. The
13+
[upgrade checklist](#upgrade-checklist) was walked on 2026-10-24 from 18.0.0;
14+
each line it measured says so. A refusal it did not reach is described as its
15+
changeset describes it.
1916
</Callout>
2017

2118
## Highlights — 18.1.0
2219

23-
- ⚠️ **A minor that breaks.** 18.1.0 is a minor version, and 90 of its
20+
- ⚠️ **A minor that breaks.** 18.1.0 is a minor version, and 91 of its
2421
changesets are declared breaking. Under the level rule the maintainer ruled
2522
on 2026-10-21 (#25105, "ruling A"), the v18 line takes no further major: a
2623
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.
7269

7370
---
7471

72+
## Release facts, as measured
73+
74+
Read with `npm view` and `npm pack` on 2026-10-24, after the publish:
75+
76+
| | Measured |
77+
| :--- | :--- |
78+
| `latest` | `18.1.0`, on every `@objectstack/*` package read (`spec`, `cli`, `runtime`, `core`, `objectql`, `skills`, `organizations`, `console`) and on `create-objectstack` |
79+
| `next` | `18.0.0-next.5`, still **older** than `latest`. ⛔ Never install `@next` for 18 |
80+
| `rc` | `17.0.0-rc.6` (`@objectstack/skills` and `@objectstack/organizations` carry no `rc` tag) |
81+
| Published | `@objectstack/cli@18.1.0` at 2026-10-24 09:06 UTC; 73 GitHub Releases, one per package, tagged `<package>@18.1.0`, between 09:12 and 09:13 UTC |
82+
| Version commit | `e649c2d91e` (#25606). The first 18.1.0 version commit, `74fa3cb6df` (#24876), was reverted before anything published (#25605) |
83+
84+
Read the 18.1.0 CHANGELOGs with two cautions:
85+
86+
- **The commit beside an entry is mostly not its own.** 260 of the 265 entries
87+
open with `3937697`, the commit of #25605 that reverted the first cut and
88+
re-added every pending changeset; only the five changesets merged after it
89+
carry their own commit. The same is true in the published tarballs: all 34
90+
entries of `@objectstack/cli@18.1.0`'s `## 18.1.0` section read `3937697`.
91+
To find the change behind an entry, search its headline in the repository's
92+
history; this page cites each entry's own commit.
93+
- **A pinned `@objectstack/cli` still ranges.** The published CLI declares
94+
eight of its `@objectstack/*` dependencies as caret ranges: `core`,
95+
`driver-memory`, `driver-mongodb`, `driver-sql`, `driver-sqlite-wasm`,
96+
`objectql`, `observability` and `runtime` (`^18.1.0` in 18.1.0, `^18.0.0` in
97+
18.0.0). An app that pins every `@objectstack/*` package exactly therefore
98+
gets a newer 18.x of those, and of the packages they depend on, as soon as one
99+
publishes. Measured: an app pinned to exactly `18.0.0` installed today holds
100+
17 `@objectstack/*` packages at 18.1.0 beside 60 at 18.0.0. See
101+
[Move the pins](#1-move-the-pins) for the override that holds them.
102+
103+
The Docker image was not read for this page.
104+
105+
---
106+
75107
## What's new in 18.1.0
76108

77109
### The version number is not the signal
@@ -88,7 +120,7 @@ else:
88120
| Everything else: REST, drivers, services, the CLI, the Console, `feat:` and `fix!:` alike | `patch` |
89121

90122
So a **patch** release can break a REST client, a boot or a deployment
91-
script, and 18.1.0 carries 42 changesets graded `patch` that say **BREAKING**,
123+
script, and 18.1.0 carries 43 changesets graded `patch` that say **BREAKING**,
92124
beside 48 graded `minor` ones. Pin exact `@objectstack/*` versions, and read the
93125
release notes for every version you cross. Each entry in a CHANGELOG states its
94126
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`,
98130
`os init` and `os create` write the exact `@objectstack/*` version
99131
(`18.1.0`, not `^18.1.0`), and `--in-repo` still writes `workspace:*`
100132
(`975d5b3`, #25539). A project scaffolded earlier keeps its caret ranges until
101-
you change them.
133+
you change them. Measured: `create-objectstack@18.1.0` wrote `"18.1.0"` for all
134+
nine `@objectstack/*` entries, and `create-objectstack@18.0.0`, run on
135+
2026-10-24, wrote `^18.0.0` and installed 18.1.0.
102136

103137
### Breaking changes you must act on
104138

105-
The 81 entries that are breaking and have no ADR-0087 conversion, and one boot
139+
The 82 entries that are breaking and have no ADR-0087 conversion, and one boot
106140
refusal (`db1c7cc`) that its changeset declares as an addition. Each line names
107141
the one fix; the CHANGELOG entry carries the full FROM → TO.
108142

@@ -231,7 +265,11 @@ door (`422 INVALID_METADATA`), where 18.0.0 accepted it.
231265
params, a single-brace `{x}`, logic or an unknown formatter inside a hole, or
232266
a blank `''`. 18.0.0 warned. Fix: write holes over `count` or the params,
233267
double a literal single brace, and delete a blank `confirmText`. A view
234-
already stored still loads.
268+
already stored still loads. *Probed on 18.1.0:* a view saved with
269+
`confirmText: 'Retitle {x} notes?'` answered `422 INVALID_METADATA` with
270+
`` A bulk action's `confirmText` reads `{{ }}` template holes (ADR-0032 §3),
271+
not the single-brace `{…}` dialect `` and named `count` and the param as the
272+
holes it binds; `'Retitle {{ count }} notes?'` saved.
235273
- **A row policy that reads `x in current_user.KEY` which nothing owns, at the
236274
permission save door** (`b531016`, #25550). It answered 200 and matched no
237275
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.
310348
refused by every data verb (`8010b64`, `72fad92`, `a1e51d6`, `fb7501b`,
311349
`882372d`). Fix: nothing if the declarations say what you mean; otherwise set
312350
`enable.apiEnabled`, or re-enable the package.
351+
- **Reassigning an approval to an address that resolves to no one is
352+
refused** (`a57643d`, #25592): `POST /api/v1/approvals/requests/:id/reassign`
353+
and `ApprovalService.reassign` answer `400 VALIDATION_FAILED` naming the
354+
address, where a `to` nobody holds was written onto the slate and the request
355+
waited on no one. A user id or email must belong to a member of the request's
356+
organization, and `position:<name>` must have a holder there; `team:`,
357+
`department:` and other addresses are always refused. Fix: reassign to a
358+
person in the organization, or to a held position.
313359
- **`POST /api/v1/meta/:type/:name/rollback` takes `toVersion` as a JSON whole
314360
number of at least 1** (`0fab7ba`, #25532), and anything else answers
315361
`400 INVALID_REQUEST`. The query form `?toVersion=3` still works. Fix: send
316-
`{"toVersion": 2}`, not `"2"` or `2.5`.
362+
`{"toVersion": 2}`, not `"2"` or `2.5`. *Measured in the walk:* `{"toVersion":"1"}`
363+
and `{"toVersion":1.5}` answered `400` with `'toVersion' (positive integer) is
364+
required`, and `{"toVersion":1}` rolled the view back.
317365
- **The Data API refuses a malformed or negative page window with `400`**
318366
naming the parameter (`17f3c65`, #25260), and the published list-query
319367
schemas declare it a non-negative integer (`5f30f00`, #25300). Fix: send a
@@ -498,14 +546,25 @@ rows.
498546
`controlled_by_parent` detail follows its master. Nothing changes for a
499547
principal that does not hold the `external` grade, and no earlier release
500548
shipped it.
549+
- **A share or sharing rule on a public object reaches an external member**
550+
(`4a25192`, #25591). On a `public_read_write` object, or a system object
551+
with no `sharingModel`, `SharingService.grant` now admits a `user` recipient
552+
who is an `external` member of the record's organization, and a sharing
553+
rule writes rows for the external members it expands to and skips the
554+
internal ones. Every other recipient keeps `422 SHARING_NOT_ENABLED`. The
555+
lint's `sharing-rule-object-not-shareable` on a public object is now a
556+
warning.
501557
- **`FlowSchema.runAs` now says what a `system` run may not write**
502558
(`025f6de`, #25564): its description names the refused writes to the four
503559
identity-assignment objects (above, under security), and ADR-0087 D3 records
504560
the change of meaning as `flow-run-as-system-identity-assignment-write-refused`,
505561
so `os migrate meta` lists it for a flow that wrote those rows. It carries no
506562
conversion, and every flow parses as before.
507563

508-
A change of meaning that also breaks is listed with the breaking changes above.
564+
#25541 and #25591 declare `Metadata-protocol: semantic` too; the GitHub Release
565+
body files them under new capabilities, because each widens what a door
566+
admits, and the counts above follow it. A change of meaning that also breaks
567+
is listed with the breaking changes above.
509568

510569
### Notable fixes in 18.1.0
511570

@@ -540,7 +599,9 @@ A change of meaning that also breaks is listed with the breaking changes above.
540599
#25370), and the scaffolds ship a starter test so a new project's `npm test`
541600
passes (`6bc8cc6`, #25236; `9c431ba`, #25196). Saving the mail provider
542601
"None (log only)" stops delivery, where the SMTP transport the boot built kept
543-
sending (`0ec4c5a`, #25497).
602+
sending (`0ec4c5a`, #25497). `POST /api/v1/data/:object/import` reads a
603+
dotted (`2011.07.01`) or compact (`20090615`) year-first day, and matches a
604+
select cell's option value in any case (`9f39094`, #25596).
544605

545606
The other fixes are in the per-package CHANGELOGs.
546607

@@ -599,26 +660,74 @@ also requires.
599660
### 18.1.0
600661

601662
<Callout type="warn">
602-
**Not walked yet.** This list is compiled from the changesets before the cut.
603-
No app has been moved from 18.0.0 to 18.1.0 for it, and no refusal below was
604-
read off a published package.
663+
**How far this list has been walked.** On 2026-10-24, from 18.0.0, once: an app
664+
scaffolded with `create-objectstack@18.0.0` (the `blank` template, pnpm, no
665+
skills), pinned to exactly `18.0.0` with the override in step 1, was booted on
666+
18.0.0 with `objectstack start` and `OS_AUTH_SECRET` set. It created a SQLite
667+
database holding one administrator, signed up through the auth API, and three
668+
notes. It was then moved to 18.1.0 from npm, and each step below was run. Each
669+
line says how it was measured:
670+
671+
- **Measured in the walk** — the step was run, and the quoted text is what
672+
18.1.0 printed.
673+
- **Probed on 18.1.0** — the walk's app carries none of it, so a probe was sent
674+
to the running 18.1.0 server, and the quoted text is its answer.
675+
676+
⛔ The walk was one small app on SQLite, under the `single` posture. Walled
677+
postures, Postgres, MySQL, Turso, a multi-environment host, an embedded kernel,
678+
a flow, a webhook and the skills sync were not walked, and no upgrade of a
679+
production application was run for this page. A line with no mark is compiled
680+
from the changesets.
605681
</Callout>
606682

607683
#### 1. Move the pins
608684

609-
Move every `@objectstack/*` dependency to `18.1.0` together, and
610-
`@objectstack/skills` with them, as in the
611-
[18.0.0 walk](/docs/releases/v18/18-0#1-move-the-pins). Pin the exact version:
612-
a caret range takes every later 18.x by itself, and a patch can break a
613-
non-protocol surface. A project `npm create objectstack` scaffolded before
614-
18.1.0 carries caret ranges: replace each `^18.x.y` with `18.1.0` and
615-
reinstall.
685+
- **Move every `@objectstack/*` dependency to `18.1.0` together**, and
686+
`@objectstack/skills` with them, as in the
687+
[18.0.0 walk](/docs/releases/v18/18-0#1-move-the-pins). Pin the exact
688+
version: a caret range takes every later 18.x by itself, and a patch can
689+
break a non-protocol surface. A project `npm create objectstack` scaffolded
690+
before 18.1.0 carries caret ranges: replace each `^18.x.y` with `18.1.0` and
691+
reinstall. The walk moved nine exact `18.0.0` pins to `18.1.0`, and
692+
`pnpm install` left no `@objectstack/*` package at 18.0.0 in the lockfile and
693+
printed no peer-dependency warning. *Measured in the walk.*
694+
- **Hold the CLI's own ranges with an override.** An exact pin does not hold
695+
the eight packages `@objectstack/cli` ranges (see
696+
[Release facts](#release-facts-as-measured)). With pnpm, add this to
697+
`pnpm-workspace.yaml`, at the version you pin:
698+
699+
```yaml
700+
overrides:
701+
'@objectstack/core': '18.1.0'
702+
'@objectstack/driver-memory': '18.1.0'
703+
'@objectstack/driver-mongodb': '18.1.0'
704+
'@objectstack/driver-sql': '18.1.0'
705+
'@objectstack/driver-sqlite-wasm': '18.1.0'
706+
'@objectstack/objectql': '18.1.0'
707+
'@objectstack/observability': '18.1.0'
708+
'@objectstack/runtime': '18.1.0'
709+
```
710+
711+
On the walk's app pinned to `18.0.0`, those eight lines (at `'18.0.0'`) took
712+
the tree from 17 packages at 18.1.0 to none. pnpm 10.28 ignored the same
713+
block under `"pnpm": { "overrides": … }` in `package.json` while
714+
`pnpm-workspace.yaml` exists. npm's `overrides` was not tried. Move the
715+
override with the pins at every upgrade. *Measured in the walk.*
616716

617717
#### 2. Before the first boot
618718

619-
- Production `os serve` or `os start`: set `OS_AUTH_SECRET`, and do not rely on
719+
- **Production `os serve`: set `OS_AUTH_SECRET`**, and do not rely on
620720
`--no-server` or `OS_MIGRATE_AND_EXIT=1` to boot without it. A kernel you
621721
embed needs it too, unless it boots with `NODE_ENV=development` or `test`.
722+
Without it, `os serve` exited `1` with
723+
`✗ This stack enables auth, but no auth secret is set, so AuthPlugin was not
724+
composed and no caller can authenticate` and the line to set it.
725+
*Measured in the walk.*
726+
- **`os start` does not refuse: it mints a secret.** With no secret set, it
727+
wrote a new one to `.objectstack/auth-secret` and booted. Boot with the
728+
secret 18.0.0 used: with the same `OS_AUTH_SECRET`, the session signed in on
729+
18.0.0 was still valid on 18.1.0; a new secret ends every session.
730+
*Measured in the walk.*
622731
- A stack that mounts no auth (`auth` tier off, `--preset minimal`) fails a
623732
production boot: enable auth.
624733
- CORS: list every cross-origin front-end in `OS_CORS_ORIGIN`, loopback origins
@@ -636,6 +745,13 @@ os build
636745
os migrate meta --from 17 # the step-18 conversions, listed for your sources
637746
```
638747

748+
On the walk's app, `os validate` and `os build` passed on 18.1.0 with no
749+
edit. `os migrate meta --from 17` exited `0` and printed
750+
`417 manual change(s) require your judgment` (18.0.0 printed 406): read it as a
751+
list to search, as on 18.0.0. `--from 18` replays nothing, and says so
752+
(`No migration step exists for protocol 18 → 18`), so keep `--from 17`.
753+
*Measured in the walk.*
754+
639755
Fix each refusal with the line in
640756
[Breaking changes you must act on](#breaking-changes-you-must-act-on) and
641757
[Converted for you](#converted-for-you-by-os-migrate-meta). The ones an app is
@@ -647,11 +763,21 @@ managed datasource.
647763

648764
#### 4. After the boot
649765

766+
- The data is as 18.0.0 left it. The walk's three notes read back, and the
767+
administrator signed in. A clean boot printed no upgrade report at the
768+
default log level. *Measured in the walk.*
650769
- Run `os migrate meta --stored` to list stored rows the step-18 conversions
651-
rewrite.
770+
rewrite. It takes no `--from` (`--stored=true cannot also be provided when
771+
using --from`); on the walk's app, which had stored no metadata, it printed
772+
`Examined 0 stored metadata row(s)` and a warning that an empty run attests
773+
nothing. *Measured in the walk.*
652774
- Run `os migrate plan`. On a database 18.0.0 created, it lists the tenant
653775
columns still nullable; the ownership ceremony tightens them to `NOT NULL`
654-
when you choose to run it.
776+
when you choose to run it. On the walk's database it printed
777+
`33 tenant column(s) are still nullable (ADR-0131 D1)`, said the ceremony
778+
tightens 31 of them, and ended
779+
`✓ Physical schema is in sync with metadata — nothing to migrate.`
780+
*Measured in the walk.*
655781
- If the boot reports a notification preference collision, resolve it with
656782
`os migrate duplicates` and restart.
657783
- Check that REST and MCP callers do not depend on a disabled package's actions

0 commit comments

Comments
 (0)