Skip to content

Commit b93b5fe

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21616-objectui-pin-bump
2 parents 87d7c51 + f97660c commit b93b5fe

38 files changed

Lines changed: 2042 additions & 257 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/driver-mongodb': patch
3+
---
4+
5+
Provenance comments in `@objectstack/driver-mongodb` cite the commits that decided them, not tracker numbers that no longer resolve
6+
7+
Clause-②: no
8+
9+
Docblocks and comments across the package cited issue-tracker numbers that now answer 404 on GitHub.
10+
Each one now cites the commit in this repository's history that made the decision it describes. One of
11+
these docblocks sits on an exported member (`MongoDBDriver.update()`), so the reworded text appears in
12+
the published `index.d.ts` / `index.d.mts`; that docblock and one more comment esbuild keeps appear in
13+
the JavaScript output (`index.js` / `index.mjs`); the sourcemaps do not change.
14+
15+
Comment only: no export, type, error code, status, message text or runtime behaviour changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
feat(spec)!: `element:text` `variant` refuses `heading` / `subheading` by name — the vocabulary is the nine `ui:text` publishes, and `os migrate meta` rewrites them to `h2` / `h3` (#21015)
7+
8+
**BREAKING** — `heading` and `subheading` leave `ElementTextPropsSchema.variant` (an
9+
`element:text` page component's `properties.variant`). This is the second release of
10+
the ruled two-release convergence on the nine values `ui:text` publishes — `h1`-`h6`,
11+
`body`, `caption`, `overline`. 17.5.0 added the nine and refused nothing; 17.6.0 was
12+
the full release in which both vocabularies parsed; this release refuses the two old
13+
spellings. A heading is a document level, not a text style: `heading` and
14+
`subheading` named a style and left the renderer to pick the level.
15+
16+
### FROM → TO
17+
18+
| removed | what to write instead |
19+
| --- | --- |
20+
| `variant: 'heading'` | `variant: 'h2'` — the heading element `heading` always rendered — or the level the page outline means. |
21+
| `variant: 'subheading'` | `variant: 'h3'` — the heading element `subheading` always rendered — or the level the page outline means. |
22+
23+
**The one-line fix: `heading` → `h2`, `subheading` → `h3`.**
24+
`os migrate meta --from 17` lists the mechanical edits for existing sources.
25+
26+
The rewrite keeps the heading ELEMENT (so the document outline is unchanged) but not
27+
the size: `heading` drew in the `h3` style and `subheading` in a medium-weight small
28+
heading style, and `h2` / `h3` draw their own, larger styles. Where the old look
29+
mattered more than the level, pick the level whose style you want.
30+
31+
Each retired spelling is refused at parse with a prescription naming the level to
32+
write, and in `tsc` (the two members are gone from the input type). Any other unknown
33+
value keeps zod's own message. An `element:text` with no `variant` still parses to
34+
`body`.
35+
36+
### The retirement kit
37+
38+
- **Value-level retirement.** The enum is declared through `enumWithRetiredValues`
39+
(`shared/retired-key.ts`), with the two prescriptions module-private. No authorable
40+
KEY and no def changed, so nothing lands in `RETIRED_KEYS_BY_MAJOR` and the four
41+
surface ratchets (`api-surface`, `authorable-surface`, `json-schema.manifest`,
42+
`api-surface-signatures`) are byte-identical; the generated component reference
43+
page drops the two values.
44+
- **D2 conversion `element-text-variant-heading-levels`** (step 18, retired from the
45+
load path): `heading` → `h2` and `subheading` → `h3` on every `element:text` page
46+
component — regions, named slots and container nesting. Stored `sys_metadata` page
47+
rows replay it at rehydration; one notice per rewritten block.
48+
- **D3 entry `element-text-variant-heading-subheading-retired`** carries the judgement
49+
the conversion cannot make: whether the rewritten level is the one the page means.
50+
- **No further deprecation window**: 17.6.0 was the window the ruling asked for.
51+
52+
### Producers moved in this repository
53+
54+
- `@objectstack/platform-objects`: the four section headings on the `sys_user` record
55+
page's Security tab (`Password & Sign-in`, `Two-Factor Authentication`, `Email
56+
Verification`, `Danger Zone`) move from `subheading` to `h3`. They render the same
57+
h3 element, in the `h3` style.
58+
- `examples/app-showcase`: the `page-variables` detail heading moves to `h3`.
59+
60+
⚠️ **The out-of-repo author population is NOT MEASURED.** `@objectstack/spec` is
61+
published, and tenant-authored pages were not measured. In this repository the five
62+
writers above were the only ones outside `packages/spec`. objectui at `main` authors
63+
neither value; its `element:text` renderer, registry `inputs` enum, html tier and the
64+
published `sdui.manifest.json` still list the two, and drop them once this release is
65+
installable there (the objectui follow-up).
66+
67+
Clause-②: no (narrowing)
68+
69+
<!-- adr-0087: registered element-text-variant-heading-levels, element-text-variant-heading-subheading-retired -->
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(metadata-protocol)!: the generic data door refuses a stored-metadata filter that reads the body or a content hash through a cross-field comparand or below its depth backstop, and exports its one filter-field collector and one search narrowing for the reader-context seam (#21544)
7+
8+
Clause-②: yes (narrowing)
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) no metadata body, authorable key, spelling, export or stored shape moves; what changes is which read-query shapes the generic data door accepts over the two stored-metadata tables, and two module functions are added to the package surface, so `objectstack migrate meta` has nothing to rewrite. The other categories are closed on facts: both packages publish (not `unpublished`); no ADR-0087 id covers a refused query shape (not `registered` / `already-registered`); and the change is runtime behaviour plus additive exports, not a declaration (not `runtime-interface-only` / `type-surface-only`). -->
11+
12+
**BREAKING**: this narrows what the generic data door (`GET /api/v1/data/:object`, `POST /api/v1/data/:object/query` and the in-process `findData`) accepts when it reads `sys_metadata` or `sys_metadata_history`. Two filter shapes read the stored body column or a content-hash column (`checksum`, `previous_checksum`, or the history table's `change_note`) without the family's refusal ever seeing them, and both ran before this release:
13+
14+
- a cross-field comparand naming one of those columns — `{ "name": { "$ne": { "$field": "metadata" } } }`, in `where` or in an aggregation's `filter`, under `$not` included. The SQL drivers evaluate it row by row, so row presence disclosed the column's value;
15+
- a filter on one of those columns nested more than 32 combinators deep, which the door's field collector stopped reading at. A body `$contains` of a stored credential answered the row and a wrong guess answered none.
16+
17+
Both now answer the door's `400 INVALID_FIELD`, naming the column, before the query runs — the answer the same filter already gets when it names the column directly. The route: filter those tables by their scalar columns (the type, the name, the state and the like), compare scalar columns with each other, and read the bodies with a plain list, which is served projected. Every other column of the two tables, and every other object, is unchanged; a dotted key into one of those columns was, and stays, refused by the door's dotted-path rule. It ships as `minor` under the launch-window convention for accept-set narrowings.
18+
19+
- **`@objectstack/metadata-protocol`** exports two module functions the generic data door now calls itself:
20+
- `collectStoredMetadataFilterFields(object, query)` — the family's one filter-field collector: every column a read query's filters read (`where`, the engine's `filter` alias and each aggregation filter): each key's head and each cross-field `{ $field }` comparand, at any depth. `[]` outside the family.
21+
- `narrowStoredMetadataSearch(object, query, schema, wireSpelling?)` — the family's one default-search narrowing: an explicit search-field list naming the body or a hash column is refused, a default search is narrowed to the searchable set without them (returned for the caller to run as `searchFields`), and a set that narrows to nothing is refused. The `StoredMetadataSearchSchema` type it reads is exported beside it.
22+
- **`@objectstack/runtime`**: the stored-metadata reader-context seam (`ctx.api.object(...)` for action and hook bodies, a handler's `ctx.api`, and `ctx.engine.find`) calls those two functions instead of its own copy of the narrowing and `@objectstack/plugin-security`'s condition walk, so the seam and the door answer every family filter and search identically. A `count` through the seam now runs the query the guard returns. The seam's accept set is unchanged: every shape it refused before it still refuses, now through the door's collector.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
'@objectstack/objectql': minor
3+
---
4+
5+
fix(objectql)!: a read with no projection serves the object's declared fields and the platform's system columns, never a column no metadata declares (#21571)
6+
7+
**BREAKING (narrowing)** — what a released read door serves shrinks. A column that
8+
no metadata declares, typically a field retired in an upgrade whose column additive
9+
schema sync leaves in the table until `os migrate apply --allow-destructive`, is no
10+
longer returned by any read through the engine.
11+
12+
| read | before | now |
13+
| --- | --- | --- |
14+
| `POST /api/v1/data/:object/query` or `GET /api/v1/data/:object` with no `fields` | every column of the table, retired ones and their values included | the declared fields, the registry's system columns, `id`, `created_at`, `updated_at` |
15+
| `GET /api/v1/data/:object/:id`, export, search hits, the RPC dispatcher, `expand`ed records | the same whole row | the same declared set |
16+
| `engine.find` / `engine.findOne` in process (hooks, flows, plugins), no `fields` | the whole row | the declared set |
17+
| an explicit `fields` naming a declared field whose column does not exist yet (driver-sql retries `select('*')`) | the whole row, retired columns included | the declared set |
18+
| `POST /api/v1/data/:object/:id/clone` of a record whose table carries a retired column | refused `INVALID_FIELD` (the copy carried the retired column into the insert) | cloned |
19+
20+
**Unchanged:** naming a retired column in `fields` still answers `400 INVALID_FIELD`
21+
on the data door. Declared fields keep their treatment: `internal: true` omission,
22+
credential masking, formula evaluation and the hidden `__search` strip run as
23+
before, and the registry-injected tenant, owner and audit columns are still served.
24+
No driver changed: the engine shapes the rows any driver returns, so the answer is
25+
the same on every driver and every door. Writes, and the rows a write returns, are
26+
not changed by this release.
27+
28+
**If you still read a retired column's values** (for example a one-time conversion
29+
that copies the old columns into their replacement field): run that conversion
30+
BEFORE upgrading to this release, while the old field is still declared, or, once
31+
it lands, read the unmapped columns through the operator-only `os migrate` read
32+
(objectstack#21573). There is no flag that re-opens undeclared columns on a runtime
33+
door. An in-process reader that needs a column must declare it as a field.
34+
35+
Clause-②: no (narrowing)
36+
37+
<!-- adr-0087: not-required (no-migration-prescription) No authorable key, spelling, export, config field or stored shape is removed or renamed, and no stored row is read or rewritten: the change narrows which physical columns the engine's read verbs return, to the field map the metadata already declares. A retired column's values stay in the table; what an app does with them is an operator action stated above (convert before upgrading, or the operator-only os migrate read), not a FROM to TO mapping that objectstack migrate meta could apply. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers a read projection (not registered / already-registered); and the change is runtime behaviour, not a declaration (not runtime-interface-only / type-surface-only). -->

‎content/docs/data-modeling/queries.mdx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,13 @@ pattern the metadata-revision / flow-run / notification list endpoints already u
285285
}
286286
```
287287

288+
Without `fields`, a read returns the object's **declared** fields plus the platform's
289+
system columns (`id`, `created_at`, `updated_at`, and the tenant, owner and audit columns
290+
the registry adds). A column no metadata declares is never returned — for example one a
291+
retired field left in the table until `os migrate apply --allow-destructive` drops it — and
292+
naming it in `fields` on the data API is refused with `400 INVALID_FIELD`. To read such a column's values
293+
for a one-time conversion, run the conversion before the field is retired.
294+
288295
### Nested / Related Fields
289296

290297
{/* os:check */}

‎content/docs/references/ui/component.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ Sort field and direction pair
436436
| Property | Type | Required | Description |
437437
| :--- | :--- | :--- | :--- |
438438
| **content** | `string \| Record<string, string>` | ✅ | Text or Markdown content — a plain string, or an inline locale map |
439-
| **variant** | `Enum<'h1' \| 'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6' \| 'body' \| 'caption' \| 'overline' \| 'heading' \| 'subheading'>` | optional (default: `"body"`) | Text style variant |
439+
| **variant** | `Enum<'h1' \| 'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6' \| 'body' \| 'caption' \| 'overline'>` | optional (default: `"body"`) | Text style variant |
440440
| **align** | `Enum<'left' \| 'center' \| 'right'>` | optional (default: `"left"`) | Text alignment |
441441
| **aria** | `{ ariaLabel?: string \| Record<string, string>; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes |
442442

‎examples/app-showcase/src/ui/pages/page-variables.page.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const PageVariablesPage = definePage({
8787
visibleWhen: "page.selectedProjectId != ''",
8888
properties: {
8989
content: '✓ Project selected',
90-
variant: 'subheading',
90+
variant: 'h3',
9191
},
9292
},
9393
{

‎packages/drivers/driver-mongodb/src/mongodb-11151-boolean-aggregand-answers.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
* ## The ruling this suite pins
88
*
99
* - **`sum` / `avg` answer arithmetic** — `3` / `0.5` over a 3-true/3-false
10-
* fixture. The #11065 family shape, landed on `driver-memory` and on every
10+
* fixture. The commit 20950404c family shape, landed on `driver-memory` and on every
1111
* SQL dialect (#11635).
1212
* - **`min` / `max` answer `0` / `1`** — #11152 (maintainer 2026-08-28,
13-
* applied on that card's comment 5448627494, ruling verbatim and
13+
* landed as commit f6fa22ce1, ruling verbatim and
1414
* untranslated: 「12745 A回,其他同意。」), SUPERSEDING #11249's
1515
* `false` / `true`: booleans aggregate as NUMBERS on every face, with no
1616
* per-aggregate exception, so one boolean column's aggregates answer in one

‎packages/drivers/driver-mongodb/src/mongodb-aggregation.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,15 +646,15 @@ export function buildAggregationPipeline(opts: {
646646
* `$sum`'s identity `0` and averaged to `null` here, while `SUM(col)` /
647647
* `AVG(col)` answer `3` / `0.5` over the same 3-true/3-false rows on every SQL
648648
* dialect (#11635), `driver-memory` answers those numbers on both of its faces
649-
* (#11065), and objectql's in-memory fallback answers them too because its
649+
* (commit 20950404c), and objectql's in-memory fallback answers them too because its
650650
* `toNumber` is `Number(v)` and `Number(true) === 1`. A rate measure over a
651651
* flag column — an SLA-violation rate, a win rate — is the ordinary shape of
652652
* that query, and the two answers are not two spellings of one: a dashboard
653653
* tile bound to the measure renders a percentage under SQL and a blank here,
654654
* indistinguishable from "no matching rows". `sum`'s `0` is the worse half,
655655
* being a plausible number rather than a visible hole.
656656
*
657-
* The expression is the one #11065 landed on `driver-memory`'s analytics face
657+
* The expression is the one commit 20950404c landed on `driver-memory`'s analytics face
658658
* (`memory-analytics.ts`, `numericAggregandExpr`), reproduced rather than
659659
* imported: this driver shares no line of code with that one, and the shared
660660
* contract between them is the VALUES in `@objectstack/spec/data`, not a

‎packages/drivers/driver-mongodb/src/mongodb-driver.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ describe.skipIf(!sharedMongod)('MongoDBDriver', () => {
155155
it('should update a record and return updated data', async () => {
156156
await driver.create('task', { id: 'upd-1', title: 'Original', status: 'new' });
157157
const result = await driver.update('task', 'upd-1', { title: 'Updated', status: 'done' });
158-
// `update()` declares `Record<string, unknown> | null` (#14428): a miss
158+
// `update()` declares `Record<string, unknown> | null` (commit ca3fd4b1a): a miss
159159
// answers `null`. This case is the FOUND arm, so pin that first and read
160160
// the fields through it -- same idiom as `findOne` above.
161161
expect(result).not.toBeNull();

0 commit comments

Comments
 (0)