Skip to content

Commit ced5c49

Browse files
docs(releases): close the two v16 content gaps — document 16.1.0 and the two GA-cut changesets (#8917) (#9039)
Strictly changeset-derived, per the 2026-08-16 maintainer ruling on #8917. Gap 1 — 16.1.0 was documented on no release page. v16.mdx gains a "Highlights — 16.1.0" block and a "What's new in 16.1.0" section following the v15 house style (Highlights block up top, detail section after the major's, one Upgrade-checklist sub-head per release), plus a "16.1 adds ..." clause on the releases index entry mirroring v15's. The release-status blockquote no longer says the minor is uncovered, and the page description names both releases. Gap 2 — two changesets shipped at the 16.0.0 GA cut appear in no RC window. A new "Landed at the 16.0.0 GA cut" section carries a line item for each: 8ff9210 (ViewFilterRule operator enum, labeled an accept-set narrowing on a published surface) and db34d54 (console pin to 9a5f016f7d5c). Both measurements re-derived from the package changelogs rather than taken from the card: 16.1.0 is 8 changesets (5 minor, 3 patch), and the id set present in the ## 16.0.0 sections but in neither ## 16.0.0-rc.0 nor ## 16.0.0-rc.1 is exactly {db34d54, 8ff9210} — confirmed keyed on entry text as well as id, since a changelog id is the sha of the commit that added the changeset and one commit can add several. Docs-only; no code, no changeset (skip-changeset). Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e449ee1 commit ced5c49

2 files changed

Lines changed: 209 additions & 5 deletions

File tree

content/docs/releases/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ migration steps, then covers new capabilities and notable fixes.
1919
## Versions
2020

2121
- [v17.0.0](/docs/releases/v17) — Files become owned `sys_file` records with server-enforced `accept`/`maxSize` and a governed download path, bulk export becomes its own opt-in privilege, the SDK is reconciled against the routes the server actually mounts (21 dead methods out, 40+ real ones in), approval nodes route approvers dynamically via CEL expressions and decision outputs, a datasource that cannot connect fails the boot, and Node 22 becomes the supported floor (current series: 17.0.0, released 2026-08-14).
22-
- [v16.0.0](/docs/releases/v16) — One org identifier (`organizationId`) across hooks and actions, quorum + per-group sign-off (会签) approvals with metadata-declared decision actions, time-relative automations, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a platform-wide enforce-or-remove sweep that makes dead metadata loud (final release: 16.1.0).
22+
- [v16.0.0](/docs/releases/v16) — One org identifier (`organizationId`) across hooks and actions, quorum + per-group sign-off (会签) approvals with metadata-declared decision actions, time-relative automations, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a platform-wide enforce-or-remove sweep that makes dead metadata loud; 16.1 adds a `requires` capability-provider preflight, two more dashboard build gates, and `runAs:'user'` automations that run with the triggering user's real grants (final release: 16.1.0).
2323
- [v15.0.0](/docs/releases/v15) — Explain record access layer by layer, a docked AI workspace in the Console, project-ready Gantt charts, and phone sign-in; 15.1 adds permission-following attachments, no-code third-party connectors, dashboard-wide filters, pinyin search, and whole-record inline editing — with materially safer multi-tenant and write-path defaults (final release: 15.1.1).
2424
- [v14.0.0](/docs/releases/v14) — ADR-0090 vocabulary convergence completed, object `enable.*` flags become real gates, admin user management, phone/SMS auth, book-audience enforcement, data-lifecycle contract, and effective-dated grants (final release: 14.8.0).
2525
- [v13.0.0](/docs/releases/v13) — Permission Model v2 (ADR-0090): Roles and Profiles converge on Positions, custom objects default to private, plus an explain engine, delegated administration, and self-serve MCP OAuth.

content/docs/releases/v16.mdx

Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: v16.0.0
3-
description: One org identifier for hook and action authors, quorum and per-group sign-off (会签) approvals, time-relative automations that actually fire, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a metadata-driven approvals inbox — plus a large enforce-or-remove sweep that makes dead metadata loud. Backend and Console notes for 16.0.0.
3+
description: One org identifier for hook and action authors, quorum and per-group sign-off (会签) approvals, time-relative automations that actually fire, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a metadata-driven approvals inbox — plus a large enforce-or-remove sweep that makes dead metadata loud. Backend and Console notes for 16.0.0 and 16.1.0.
44
---
55

66
**The v16 line** converges the developer surface and makes declared metadata
@@ -18,8 +18,9 @@ dashboard widget typos, dead hook events, phantom webhook triggers, unknown
1818
> 2026-07-19 and 2026-07-20). `changeset pre exit` ran with that cut, so the
1919
> `@objectstack/*` packages no longer publish as `16.0.0-rc.N`. The v16 line is
2020
> closed: `16.1.0` followed on 2026-07-22 and is its final release, and the
21-
> current series is [17.0.0](/docs/releases/v17). This page describes the
22-
> 16.0.0 content; the 16.1.0 minor is not covered here. (15.1.1 was a small
21+
> current series is [17.0.0](/docs/releases/v17). This page covers both
22+
> releases — 16.0.0 first, then
23+
> [What's new in 16.1.0](#whats-new-in-1610). (15.1.1 was a small
2324
> patch on the previous line — better-auth family pinning and auth-plugin init
2425
> isolation — covered by the [v15 page](/docs/releases/v15).)
2526
@@ -75,6 +76,42 @@ dashboard widget typos, dead hook events, phantom webhook triggers, unknown
7576
`claude mcp add` command — and the long-lived stdio transport now requires
7677
an API-key principal (fail-closed) instead of reading data unscoped.
7778

79+
## Highlights — 16.1.0
80+
81+
- **A missing capability provider fails the build, not the boot.** `os build`
82+
and `os validate` now preflight every `requires` entry: one whose provider
83+
has no installable version in the active edition (`ai`
84+
`@objectstack/service-ai`, cloud-only) fails fast with an edition-aware
85+
message, and an absent-but-installable provider becomes an advisory
86+
`pnpm add` hint. Providers were only resolved at `serve`/`start` before, so
87+
a `validate && build && test` script passed and the app crashed on boot.
88+
- **`runAs: 'user'` flows run with the user's real grants.** A
89+
record-change-triggered `runAs:'user'` flow ran its data nodes with a
90+
zero-grant principal — the triggering user's permission sets and positions
91+
were never resolved — so a `private` object 403'd the in-flow write and a
92+
`public_read_write` object silently stripped readonly/FLS-gated fields. The
93+
engine now resolves that user's actual positions and permission sets at run
94+
setup.
95+
- **Two more dashboard mistakes are build errors.** Header and widget actions
96+
pointing at an action or route that does not exist are flagged (a
97+
`script`/`modal` target errors; an in-app `url` target warns), and every
98+
dashboard-level filter must resolve to a real field on each bound widget's
99+
dataset object — previously a button that rendered and did nothing, and SQL
100+
that crashed the widget at render time.
101+
- **Import/Export works on the business-unit objects.** The Import/Export
102+
buttons Setup shows for `sys_business_unit` and `sys_business_unit_member`
103+
both returned `405 OBJECT_API_METHOD_NOT_ALLOWED`; both objects now declare
104+
`import`/`export`, so the HRIS org-tree sync imports units and memberships
105+
together.
106+
- **Admin-gated `Server-Timing` finally emits on `os serve` / `os dev`.** The
107+
per-request path — an admin sends `X-OS-Debug-Timing`, an ordinary user gets
108+
nothing — never emitted on the shipped server; only the global mode, which
109+
discloses to every caller, worked.
110+
- **Console:** record hits on the full search page and in ⌘K, `globalActions`
111+
label overlays on record-detail action bars, injected `owner_id` kept out of
112+
auto-generated list columns, and sort repeater rows rendered for union
113+
schemas.
114+
78115
---
79116

80117
# 16.0.0 in detail
@@ -792,6 +829,149 @@ pin advancing `94d4876 → af1b0db` (objectui #2706–#2736), bundled as
792829
flow `keyValue`/`numberList`, ActionParamDialog upload guard, system-field
793830
classifier) are included in this pin.
794831

832+
## Landed at the 16.0.0 GA cut
833+
834+
Two changesets are in the `## 16.0.0` changelog sections but in **neither**
835+
`16.0.0-rc.0` nor `16.0.0-rc.1`: they landed between the last RC and the GA
836+
cut on 2026-07-21, so they ship in 16.0.0 without appearing in any RC.
837+
838+
- **`ViewFilterRule.operator` becomes a closed enum — an accept-set narrowing
839+
on a published surface (#3373, changeset `8ff9210`).** The operator was
840+
previously an open string, so views could persist operators the runtime
841+
cannot evaluate. The Zod schema now constrains it to the supported operator
842+
enum and normalizes the known legacy aliases to their canonical form on
843+
parse. This is a public spec/api-surface change
844+
(`packages/spec/api-surface.json`); it landed on `main` in #3373 without a
845+
changeset, and the backfill is what shipped it with the GA instead of
846+
leaving it stranded.
847+
- **Console pin advanced to `9a5f016f7d5c` (changeset `db34d54`, objectui
848+
range `69fa5d163a97...9a5f016f7d5c`).** Nested-array columns in the flow
849+
designer's node property form (objectui#2761); the record-list "Add View"
850+
flow redone — empty-name 405, invisible drafts, canonical naming
851+
(objectui#2768); field-type-aware operators and values for the view filter
852+
in `SchemaForm` (objectui#2766); dashboard chart bars drawn on first paint
853+
(objectui#2759); and the non-atomic batch fallback gated on the discovery
854+
`transactionalBatch` capability (objectui#2755).
855+
856+
---
857+
858+
# What's new in 16.1.0
859+
860+
16.1 is a small fast-follow minor — **8 changesets (5 minor, 3 patch), no
861+
major** — released on 2026-07-22, and the v16 line's final release. Three of
862+
the five minors add a new `os build` / `os validate` gate, so its practical
863+
theme is moving configuration failures earlier: from a boot crash or a broken
864+
render to a build error. The bundled Console advances one pin,
865+
`9a5f016f7d5c → cf2d56e32a11`.
866+
867+
## New capabilities in 16.1.0
868+
869+
- **`requires` capabilities are preflighted against installable providers
870+
(#3366).** A listed capability was only checked at `serve`/`start` time, and
871+
a missing provider produced a generic "not installed — add it to your
872+
dependencies" error even when the provider has **no installable version in
873+
the current edition**; `os validate` (token vocabulary only) and `os build`
874+
(never resolved providers) both passed, so a `validate && build && test` CI
875+
script never caught it and it surfaced as an opaque boot crash — seen
876+
upgrading an open-edition app from `14.7` to `16` after
877+
`@objectstack/service-ai` went cloud-only (ADR-0025).
878+
`@objectstack/spec/kernel` now exports `PLATFORM_CAPABILITY_PROVIDERS`
879+
(token → provider package + edition) and a pure
880+
`classifyRequiredCapability()`, one machine-readable source of truth for
881+
provider/edition knowledge the serve resolver previously encoded informally.
882+
`os build` and `os validate` gained the preflight: no installable version in
883+
the active edition is a fast, edition-aware failure; absent-but-installable
884+
is an advisory `pnpm add` hint, not a hard error; a satisfied `requires` list
885+
passes unchanged. The `os serve` boot error renders the same
886+
classification, so preflight and boot read identically.
887+
- **Dead action and route references in dashboards are flagged (#3367,
888+
ADR-0049 applied to references).** `os validate` / `os build` run a new
889+
`validateDashboardActionRefs` gate over every dashboard `header.actions[]`
890+
and widget `actionUrl`. `actionType: 'script' | 'modal'` is an **error**
891+
unless `actionUrl` resolves to a defined action (`stack.actions` or an
892+
object's `actions`); `modal` also resolves via the runtime
893+
`<verb>_<object>` convention (`create_`/`new_`/`add_`/`edit_`/`update_` plus
894+
a real object) and bare object names. A dangling target ships a button that
895+
renders and silently does nothing on click — a false affordance.
896+
`actionType: 'url'` is a **warning** when a relative in-app path names an
897+
`objects`/`reports`/`dashboards`/`pages`/`views` route whose target does not
898+
exist in the stack; external URLs, interpolated (`${…}`) targets and opaque
899+
routes are skipped.
900+
- **Dashboard filter fields are validated at build time (#3365, extending
901+
ADR-0021).** `validateWidgetBindings` now checks that every dashboard-level
902+
filter (`dateRange` and each `globalFilters[]`) resolves to a real field on
903+
each bound widget's dataset object. Since #2501 wired these filters into
904+
every widget's analytics query, a filter field absent on a widget's object —
905+
a `dateRange` bound to `close_date` inherited by an account or contact
906+
widget over a different object — emitted invalid SQL (`no such column:
907+
close_date`) and crashed the widget at render time. The new rule
908+
`dashboard-filter-field-unknown` fails the build with a message naming the
909+
dashboard, widget, filter, field and object, unless the widget opts out via
910+
`filterBindings: { <name>: false }` or re-targets to an existing field.
911+
Effective-field resolution matches the runtime, and registry-injected system
912+
fields (`created_at`, the `dateRange` default) and objects outside the
913+
validated stack never false-positive.
914+
915+
## Behavior changes & fixes in 16.1.0
916+
917+
- **`runAs: 'user'` flows execute data ops with the triggering user's real
918+
permission sets and positions (#3356, follow-up to #1888).** Since #1888 the
919+
automation engine honours `flow.runAs`, but the `runAs:'user'` credential
920+
propagation was hollow: a record-change-triggered run executed its data
921+
nodes (`update_record`, …) with a **zero-grant** principal — only the
922+
`member`/`everyone` baseline — even when the triggering user was fully
923+
authorized. Two faces by object config: a `private` object 403'd the in-flow
924+
write (`not permitted for positions [org_member, everyone]`), and a
925+
`public_read_write` object let the write through but **silently stripped**
926+
readonly/FLS-gated fields. `@objectstack/core` now exports
927+
`resolveUserAuthzGrants(ql, userId, opts)` — the single place that reads
928+
`sys_member` / `sys_user_position` / `sys_*_permission_set` — which the HTTP
929+
resolver delegates to unchanged, and `AutomationEngine.setUserGrantsResolver`
930+
wires it so a `runAs:'user'` run whose trigger left the envelope unresolved
931+
resolves the user's positions and permission sets once at run setup and
932+
threads them into every data node. Contexts that already carry `permissions`
933+
are left untouched (a REST trigger, and an ADR-0090 agent ceiling acting
934+
on-behalf-of a user), so a deliberately narrowed identity is never
935+
re-broadened; `runAs:'system'` is unchanged, and a resolver error fails safe
936+
— it warns and keeps the bare user, never elevates.
937+
- **Import/Export on `sys_business_unit` and `sys_business_unit_member`
938+
(#3025, #3391 P0).** The Business Units list (Setup → Business Units)
939+
surfaces Import/Export buttons, but both objects declared an
940+
`enable.apiMethods` whitelist of only the five CRUD verbs, and the REST data
941+
plane gates import/export on that whitelist (ADR-0049) — so both buttons
942+
returned `405 OBJECT_API_METHOD_NOT_ALLOWED`. Both objects now declare
943+
`import` and `export`. The pairing matters: the HRIS org-tree sync scenario
944+
imports the units *and* their memberships together, so fixing only the unit
945+
object left the membership path still 405'ing. Reconcile-safe —
946+
`reconcileManagedApiMethods` only strips generic write verbs and never
947+
touches `import`/`export`.
948+
- **Admin-gated `Server-Timing` emits on the standard server (#3361).** The
949+
per-request path from #2408 — an admin sends `X-OS-Debug-Timing: 1` (or
950+
`json`) and gets phase timings while an ordinary user gets nothing — never
951+
emitted on the shipped Hono server: the disclosure gate is flipped by the
952+
runtime dispatcher, but the data and metadata routes on `os serve` / `os
953+
dev` are served by `@objectstack/rest`'s `RestServer`, whose identity
954+
resolver never opened it. Only global mode (`OS_SERVER_TIMING=true`), which
955+
discloses to *every* caller, worked. The disclosure predicate
956+
`isPerfDisclosurePrincipal(ec)` now lives in `@objectstack/observability`
957+
(re-exported from `@objectstack/runtime` for back-compat) as the single
958+
definition of who may pull per-request timings, and both
959+
`RestServer.resolveExecCtx` and the standalone
960+
`@objectstack/plugin-hono-server` CRUD surface open the gate for an
961+
admin/service principal via the carried `posture` rung.
962+
963+
## New in Console (Studio) — objectui pin `9a5f016f7d5c → cf2d56e32a11`
964+
965+
- Record hits surface on the full search page, with i18n group labels
966+
(objectui#2776), and in the command palette from `/api/v1/search`
967+
(objectui#2772, pairing framework#3371).
968+
- `globalActions` label overlays apply on record-detail action bars
969+
(objectui#2770).
970+
- Injected `owner_id` stays out of auto-generated list columns
971+
(objectui#2779).
972+
- `SchemaForm` renders sort repeater rows for union schemas (objectui#2771,
973+
pairing framework#3379).
974+
795975
## Upgrade checklist
796976

797977
### 16.0.0
@@ -833,6 +1013,24 @@ pin advancing `94d4876 → af1b0db` (objectui #2706–#2736), bundled as
8331013
- **Console hosts:** import spec schema values from `@objectstack/spec`
8341014
instead of the removed `@object-ui/types` `spec/ui` re-exports.
8351015

1016+
### 16.1.0
1017+
1018+
- **Re-run `os build` / `os validate` after upgrading** — three new preflights
1019+
can fail a stack that built clean on 16.0.0: a `requires` capability whose
1020+
provider has no installable version in your edition (hard error; an
1021+
absent-but-installable provider is only an advisory hint), a dashboard
1022+
`script`/`modal` action whose `actionUrl` names no defined action (error,
1023+
with unresolvable in-app `url` targets warning), and a dashboard filter
1024+
naming a field absent from a bound widget's dataset object (new rule
1025+
`dashboard-filter-field-unknown` — opt a widget out with
1026+
`filterBindings: { <name>: false }` or re-target the filter).
1027+
- **Automations:** `runAs:'user'` flows now execute data nodes with the
1028+
triggering user's real permission sets and positions. Review any flow that
1029+
depended on the previous zero-grant behavior — writes that used to 403 on a
1030+
`private` object now succeed, and readonly/FLS-gated fields that used to be
1031+
silently stripped on a `public_read_write` object are now written when the
1032+
user is authorized. `runAs:'system'` is unchanged.
1033+
8361034
## References
8371035

8381036
ADR-0099 (posture-authoritative tenant wall) · ADR-0101 (MCP stdio
@@ -843,4 +1041,10 @@ ADR-0034 (atomic batch) · ADR-0049 / #2377 (enforce-or-remove) · ADR-0059
8431041
#3266/#3268 (approvals quorum/会签) · #2678 (objectui declared-actions
8441042
inbox) · #1874 (time-relative trigger) · #1868 (filtered roll-ups) · #1928
8451043
(expression guardrails) · #1752 (drill ranges) · #2408 (Server-Timing) ·
846-
#3280/#3290 (organizationId).
1044+
#3280/#3290 (organizationId) · #3373 (ViewFilterRule operator enum).
1045+
1046+
**16.1.0** — ADR-0025 (edition boundary) · #3366 (`requires` provider
1047+
preflight) · #3367 (dead dashboard action/route references) · #3365
1048+
(dashboard filter field existence) · #3356 / #1888 (`runAs:'user'` grants) ·
1049+
#3361 / #2408 (per-request `Server-Timing` on the standard server) · #3025 /
1050+
#3391 (business-unit import/export).

0 commit comments

Comments
 (0)