@@ -14,7 +14,10 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
1414 Console" and drop each "Known console issues" line the new pin fixes
1515 (objectui 0858267e, 8001068b, 3ae91930); if it did not, record the
1616 accepted-for-GA waiver the release-readiness rule asks for;
17- 5. update v17/index.mdx (status blockquote, per-release list, checklist links).
17+ 5. if #21158 (the guest anchor's bindings for anonymous callers) lands
18+ before the cut, replace the "no supported channel" lines in Highlights,
19+ the deny-baseline Migration and the checklist with its grant channel;
20+ 6. update v17/index.mdx (status blockquote, per-release list, checklist links).
1821 Delete this comment when done.
1922*/ }
2023
@@ -25,9 +28,11 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
2528 caller that carries a principal but resolves no permission set used to be
2629 admitted to every object no set grants, with every field served as stored.
2730 It is now refused on every object, served gated fields masked or not at all,
28- and refused any query on them. ⚠️ ** App-declared anonymous endpoints and
29- embedders that set ` fallbackPermissionSet: null ` must grant a permission set
30- before upgrading.**
31+ and refused any query on them. ⚠️ ** An embedder that sets
32+ ` fallbackPermissionSet: null ` must grant its users a permission set before
33+ upgrading, and an app-declared anonymous endpoint (` authRequired: false ` )
34+ can no longer read or write objects: no supported channel grants anonymous
35+ callers a permission set yet (#21158 , open).**
3136- ** Field-level security reaches every query door** — cross-field comparands,
3237 the activity stream, the compliance ledger, approval snapshots and analytics
3338 (` de8cd58 ` , #20954 ; ` 1ecb871 ` , #21179 ; ` 1571aed ` , #20931 ). And
@@ -38,7 +43,8 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
3843 door, audit copies, realtime events and MCP stdio (` cfad7de ` , #21115 ;
3944 ` 336e191 ` , #21144 ; ` 3ddd3d0 ` , #21228 ). Run
4045 ` os migrate audit-metadata-bodies --apply ` to rewrite the copies already at
41- rest. ` datasource ` and ` external_catalog ` metadata on ` /api/v1/meta ` now need
46+ rest. Reads of ` datasource ` and ` external_catalog ` metadata, and writes of
47+ ` datasource ` metadata, on ` /api/v1/meta ` now need
4248 ` manage_platform_settings ` (` 454bbb6 ` , #21148 ; ` 7a606a9 ` , #21119 ).
4349- ** Analytics is judged like the data door** — anonymous ` 401 ` , hidden and
4450 masked fields ` 403 ` , related objects admitted and row-scoped, structured,
@@ -54,8 +60,9 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
5460 ` '' ` and ` [] ` ** (` f1e921a ` , #20570 ).
5561- ** Temporal values follow one rule at every door:** a ` datetime ` names a UTC
5662 year from 1000 to 9999, a ` time ` is a zone-less wall clock, and an import
57- reads ` date ` / ` datetime ` / ` time ` cells only in ISO 8601 and the export's
58- own shape (` 05a7547 ` , #20843 ; ` 63bfe69 ` , #20721 ; ` eb4b17c ` , #20601 ).
63+ reads ` date ` / ` datetime ` / ` time ` cells only in ISO 8601, the export's own
64+ shape or a year-first date (` 05a7547 ` , #20843 ; ` 63bfe69 ` , #20721 ; ` eb4b17c ` ,
65+ #20601 ).
5966- ** One rule decides which flow is packaged.** A packaged flow wins over a
6067 stored flow of the same name at every startup step (` 75519e1 ` , #20942 ), so ⚠️
6168 ** a stored flow that shares a packaged flow's name stops running.** Flows
@@ -69,7 +76,7 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
6976 tenant database — applies the caller's tenant scope (` 4b59a38 ` , #21245 ), and
7077 a tenant-scoped ` upsert ` never merges into another organization's row
7178 (` 95e24b0 ` , #21225 ). The Turso remote transport issues ` auto_number ` values
72- (` e35c40a ` , #21160 ), and MySQL stores ` sys_jwks ` and ` sys_member ` rows again
79+ (` e35c40a ` , #21160 ), and MySQL stores ` sys_jwks ` and ` sys_member ` rows
7380 (` 95b91cc ` , #21272 ).
7481- ** Shared picklists:** a new ` picklist ` metadata kind that fields reference by
7582 name and other packages can extend (` addbbf0 ` , #20823 ; ` 88b484e ` , #21047 ).
@@ -107,8 +114,9 @@ explicitly breaking. Several things in this release change behaviour on a
107114 included, only rows about records they can read — every ` delete ` row and
108115 sign-out row drops out of the data API;
109116- stored metadata bodies are redacted on the data door, in audit copies,
110- realtime events and MCP stdio, and ` datasource ` / ` external_catalog ` metadata
111- on ` /api/v1/meta ` needs ` manage_platform_settings ` ;
117+ realtime events and MCP stdio, and reading ` datasource ` / ` external_catalog `
118+ metadata (or writing ` datasource ` ) on ` /api/v1/meta ` needs
119+ ` manage_platform_settings ` ;
112120- analytics widgets that read a hidden or masked field, or a related object the
113121 caller may not read, answer ` 403 ` ; a time dimension that declares one
114122 granularity is bucketed by it;
@@ -118,7 +126,8 @@ explicitly breaking. Several things in this release change behaviour on a
118126- 「is empty」 in a stored sharing rule or view also matches ` '' ` and ` [] ` , so
119127 such a sharing rule shares more records;
120128- a ` datetime ` write before year 1000, a ` time ` value with an offset, and an
121- import cell in a non -ISO date spelling are refused ;
129+ import date cell that is not ISO 8601, the export ' s shape or year-first
130+ (` 07/15/2026 ` , an Excel serial ) are refused ;
122131- a caller -supplied ` formula ` value is stripped from every write ;
123132- a stored flow that shares a packaged flow's name stops running; the toggle
124133 refuses customer-authored flows with ` 409 ` ; enabling a packaged flow whose
@@ -169,8 +178,10 @@ filtered, sorted, grouped, aggregated and written.
169178- ** Fields gated by ` requiredPermissions ` ** (` 665cab3 ` , #21134 ). The field is
170179 not served (or is served masked, if it also declares a ` maskingRule ` ). A
171180 filter, sort key, group key, aggregate or write payload naming it is refused
172- ` 403 PERMISSION_DENIED ` . ` getReadableFields ` , ` getQueryableFields ` ,
173- ` getWritableFields ` and ` getMetadataReadableFields ` no longer list it.
181+ ` 403 PERMISSION_DENIED ` . ` getQueryableFields ` and ` getWritableFields ` no
182+ longer list it; ` getReadableFields ` lists it only when a ` maskingRule ` serves
183+ it masked, and ` getMetadataReadableFields ` drops it when the deployment's
184+ fallback set resolves to nothing.
174185- ** Fields with a ` maskingRule ` ** (` a9d36d5 ` , #21051 ). The field is served
175186 masked and is not queryable. A write that sends the masked placeholder back
176187 is refused ` 400 VALIDATION_ERROR ` .
@@ -185,7 +196,10 @@ users of a stock `objectstack serve` are not affected.
185196
186197- App-declared anonymous endpoints (` authRequired: false ` ) can no longer read
187198 or write objects until the ` guest ` anchor's bindings resolve for anonymous
188- callers (#21158 ). Give those callers a permission set before you upgrade.
199+ callers (#21158 ). That issue is open, and until it lands no administrator
200+ binding grants an unauthenticated caller a permission set, so there is no
201+ supported migration: an app that depends on such an endpoint reading or
202+ writing objects should hold the upgrade.
189203- An embedder that sets ` fallbackPermissionSet: null ` must grant signed-in
190204 users a permission set explicitly.
191205- A caller that needs a gated field needs a permission set holding all of the
@@ -273,8 +287,8 @@ including a `datasource` body's credential material. That body was served as
273287stored on several exits. It is now projected through the same redactor the
274288` /meta ` exits use:
275289
276- - ** the generic data door** (` cfad7de ` , #21115 ): ` GET ` and ` POST ` on
277- ` /api/v1/data/:object ` , the by-id read, and the export route. A body whose row
290+ - ** the generic data door** (` cfad7de ` , #21115 ): ` GET /api/v1/data/:object ` ,
291+ ` POST /api/v1/data/:object/query ` , the by-id read, and the export route. A body whose row
278292 has no ` type ` , or that fails to parse while its type registers a redactor, is
279293 omitted. ` ?select=metadata ` still works.
280294- ** audit copies and realtime events** (` 336e191 ` , #21144 ): ` sys_audit_log ` 's
@@ -314,8 +328,8 @@ definitions there.
314328unaffected.
315329
316330** Migration.** Grant ` manage_platform_settings ` , through a permission set, to
317- every user or integration that reads or writes these types through
318- ` /api/v1/meta ` while holding only
331+ every user or integration that reads either type, or writes ` datasource ` ,
332+ through ` /api/v1/meta ` while holding only
319333` manage_metadata ` , ` studio.access ` or ` setup.access ` — or route those calls
320334through a caller that already holds it.
321335
@@ -335,7 +349,7 @@ strategies.
335349 (` 1571aed ` , #20931 ). Dimensions, measures, time dimensions, filter members,
336350 order keys, joined members and a dataset's own and requested measures'
337351 filters are judged; a hidden one answers ` 403 PERMISSION_DENIED ` . Before,
338- the native-SQL strategy answered, and ` /sql ` printed the statement .
352+ the native-SQL strategy answered such queries .
339353- ** Masked fields cannot be grouped, aggregated, filtered or sorted**
340354 (` 83480c6 ` , #20955 ); they answer ` 403 ` . If the security service cannot say
341355 which fields are queryable, every field declaring a ` maskingRule ` is treated
@@ -365,9 +379,11 @@ strategies.
365379 (` 58a77db ` , #21117 ), as the data door does. Before, a compiled read scope
366380 matched the stored JSON text as a substring: on SQLite a policy could admit
367381 rows outside it, and on PostgreSQL every query under it answered ` 500 ` .
368- - ** A dataset ` field ` that is not a column reference is refused ` 403 ` ** at the
369- dataset door, for every caller and with or without a security service
370- (` ce4e205 ` , #21190 ).
382+ - ** A dataset ` field ` that is not a column reference is refused at the
383+ dataset door** , for every caller and with or without a security service
384+ (` ce4e205 ` , #21190 ). Since ` 434c6c7 ` (#21240 ) the door parses the dataset
385+ first and answers ` 400 VALIDATION_FAILED ` ; the ` 403 ` remains only for a
386+ stored row that reaches the service without that parse.
371387
372388** What may be grouped, counted and aggregated**
373389
@@ -384,8 +400,9 @@ strategies.
384400- ** Every cube measure is checked against the aggregate × field-type table**
385401 (` 39ab294 ` , #21128 ), and so is a measure over a relationship path such as
386402 ` account.name ` (` 3a7b6eb ` , #21230 ). ` min ` / ` max ` over a non-numeric,
387- non-temporal, non-boolean type, and ` sum ` / ` avg ` over a non-numeric type
388- (or ` sum ` over ` percent ` ), answer ` 400 INVALID_FIELD ` instead of a raw value
403+ non-temporal, non-boolean type, and ` sum ` / ` avg ` over a type outside the
404+ numeric and boolean classes (or ` sum ` over ` percent ` ), answer `400
405+ INVALID_FIELD` instead of a raw value
389406 typed ` number ` , a ` 0 ` or a ` 500 ` . ` min ` / ` max ` over ` date ` , ` datetime ` or
390407 ` time ` is now described ` fields[] { type: 'time' } ` , not ` number ` , and a
391408 related numeric ` min ` / ` max ` on PostgreSQL returns a number instead of an
@@ -492,8 +509,8 @@ before any driver is asked. The error names the position (`groupBy[0]`,
492509- ** ` groupBy ` on a structured-JSON field** — ` json ` , ` composite ` , ` repeater ` ,
493510 ` record ` , ` location ` , ` address ` , ` vector ` (` 157baa7 ` , #20804 ).
494511- ** ` groupBy ` on a multi-value field** — ` multiselect ` , ` checkboxes ` , ` tags ` ,
495- or a ` select ` / ` lookup ` / ` user ` / ` file ` / ` image ` declared `multiple:
496- true` — **and ` count_distinct` over a JSON-stored or multi-value field**
512+ or a ` select ` / ` radio ` / ` lookup ` / ` user ` / ` file ` / ` image ` declared
513+ ` multiple: true` — ** and ` count_distinct ` over a JSON-stored or multi-value field**
497514 (` 975b248 ` , #20911 ).
498515- ** ` min ` / ` max ` / ` avg ` over a type the aggregate × field-type table refuses**
499516 (` a75311d ` , #21037 ). ` min ` / ` max ` accept ` number ` , ` currency ` , ` percent ` ,
@@ -524,8 +541,8 @@ are the hand-migrations already registered under
524541#### A filter is refused where it used to answer the wrong rows
525542
526543Each of these used to return every row, no rows, a text comparison or a
527- driver's own ` 500 ` . Each now answers ` INVALID_FILTER ` / ` 400 ` at ` where ` , a
528- per-aggregation ` filter ` and ` having ` , before any driver read :
544+ driver's own ` 500 ` . Each now answers ` INVALID_FILTER ` / ` 400 ` — at ` where ` , a
545+ per-aggregation ` filter ` and ` having ` unless the item names its positions :
529546
530547- ** An object with no ` $ ` operator under a scalar field** (`{ " amount" : { " a" :
531548 1 } } ` , or ` {} ` ) ( ` 97005ae`, #20744 ), and ** under a structured-JSON field or
@@ -543,17 +560,19 @@ per-aggregation `filter` and `having`, before any driver read:
543560 (` dcd3309 ` , #21065 ) or past what a JavaScript ` Date ` can hold (` 1bd14c9 ` ,
544561 #21123 ), such as ` {300000_years_ago} ` .
545562- ** ` $startsWith ` , ` $endsWith ` , ` $icontains ` , ` $like ` or ` $ilike ` on a
546- JSON-stored or multi-value field** (` 2c1cef3 ` , #21165 ). SQLite matched the
547- serialized text (` $startsWith: "[" ` matched every valued row) and PostgreSQL
548- answered ` 500 ` .
563+ JSON-stored or multi-value field** , in the SQL drivers' ` where ` (Turso's
564+ local transport included) and the per-aggregation ` filter ` (` 2c1cef3 ` ,
565+ #21165 ). SQLite matched the serialized text (` $startsWith: "[" ` matched every
566+ valued row) and PostgreSQL answered ` 500 ` .
549567- ** ` $eq ` , ` $ne ` , orderings, ` $between ` , ` $in ` , ` $nin ` or implicit equality on
550568 a JSON-stored field inside a per-aggregation ` filter ` ** (` a11faee ` , #21097 ),
551569 as ` where ` already refused. ` { owners: { $in: ['u1', 'u9'] } } ` counted ` 0 ` ,
552570 and ` $nin ` counted the rows it was asked to exclude.
553- - ** A non-boolean ` $exists ` or ` $null ` ** in the in-memory and MongoDB drivers
554- (` a3dc817 ` , #20979 ) and in a per-aggregation ` filter ` or ` having `
555- (` c35436c ` , #21157 ). ` "yes" ` , ` 1 ` and the string ` "false" ` were read by
556- truthiness.
571+ - ** A non-boolean ` $exists ` ** in the in-memory and MongoDB drivers, which
572+ read anything but ` true ` as "has no value" (` a3dc817 ` , #20979 ), and ** a
573+ non-boolean ` $exists ` or ` $null ` ** in a per-aggregation ` filter ` or ` having ` ,
574+ where ` "yes" ` , ` 1 ` and the string ` "false" ` were read by truthiness
575+ (` c35436c ` , #21157 ).
557576
558577** Migration.** Compare a field with a value or an operator; to filter by a
559578related record, name a relation field. Send temporal comparands in ISO 8601
@@ -600,9 +619,10 @@ payloads.
600619- ** A ` time ` value is a zone-less wall clock.** A ` time ` field refuses a value
601620 carrying ` Z ` or an offset (` "10:00Z" ` , ` "10:00+08:00" ` ) and an instant whose
602621 UTC year has no four-digit spelling (` 63bfe69 ` , #20721 ), with
603- ` VALIDATION_FAILED ` / ` 400 ` (` invalid_time ` ). A ` time ` field default, a
604- ` time ` action-param default and a submitted ` time ` action param are refused
605- the same way where they are authored or submitted (` c9c182e ` , #20763 ). On
622+ ` VALIDATION_FAILED ` / ` 400 ` (` invalid_time ` ). A zone-suffixed ` time ` field
623+ default or ` time ` action-param default is refused when the schema parses it,
624+ and a submitted ` time ` action param with a zone answers ` invalid_shape `
625+ (` c9c182e ` , #20763 ). On
606626 memory and SQLite, zone-suffixed values used to be stored verbatim and read
607627 back differently.
608628- ** ` POST /api/v1/data/:object/import ` reads ` date ` , ` datetime ` and ` time `
@@ -1456,13 +1476,16 @@ been given.
14561476
14571477** Before you upgrade**
14581478
1459- - ** Give a permission set to every caller that carries a principal but
1460- resolves none:** callers of app-declared anonymous endpoints
1461- (` authRequired: false ` ) that read or write objects, and signed-in users on an
1462- embedder that sets ` fallbackPermissionSet: null ` . After the upgrade they are
1463- refused every object. * Not exercised.*
1479+ - ** Find every app-declared anonymous endpoint (` authRequired: false ` ) that
1480+ reads or writes objects.** After the upgrade it is refused every object, and
1481+ no supported channel can grant anonymous callers a permission set until
1482+ #21158 lands; hold the upgrade if you depend on one. * Not exercised.*
1483+ - ** Grant a permission set to signed-in users on an embedder that sets
1484+ ` fallbackPermissionSet: null ` ;** without one they are refused every object.
1485+ * Not exercised.*
14641486- ** Grant ` manage_platform_settings ` ** to every user or integration that reads
1465- or writes ` datasource ` or ` external_catalog ` metadata through ` /api/v1/meta `
1487+ ` datasource ` or ` external_catalog ` metadata, or writes ` datasource ` , through
1488+ ` /api/v1/meta `
14661489 while holding only ` manage_metadata ` , ` studio.access ` or ` setup.access ` . * Not
14671490 exercised.*
14681491- ** List stored flows that share a packaged flow's name** — startup warnings or
0 commit comments