Skip to content

Commit 4e3e4f5

Browse files
committed
Merge origin/main into claude/issue-20515-orgless-grants-global-only
Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
2 parents 6b3414e + 288611e commit 4e3e4f5

189 files changed

Lines changed: 5744 additions & 2591 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/19332-g2a-fieldgroups-indexes-form-rows.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Two live structured object keys are authorable in the metadata form: `fieldGroup
1010
- `fieldGroups` (Basics, beside `highlightFields`) — six sub-rows, one per canonical group key: `key` and `label` (required text), `icon` (text), `description` (textarea), `collapse` (a `none` / `expanded` / `collapsed` select) and `visibleWhen` (`type: 'code'`, `language: 'expression'`, the `fields` grid's predicate rows). The three `[DEPRECATED → collapse]` aliases (`defaultExpanded`, `collapsible`, `collapsed`) are **not** offered; the metadata-form reconciliation ledger records a nested `omit` row for each. The parse still accepts them and derives `collapse` from one only when `collapse` is absent, so a stored entry keeps its meaning, and a `collapse` set in the form outranks any alias it carries.
1111
- `indexes` (Advanced, beside `datasource`) — three sub-rows over the keys the SQL driver reads: `name` (text), `fields` (`widget: 'string-tags'`, required) and `unique`, a select offering **only** `global` and `organization`. The deprecated bare `unique: true` is never offered: a schema-derived control would take the union's first arm and render a switch that writes it. An edit merges into the stored entry, so an index that already carries `true` or `false` keeps it until the author picks a scope, and the select can write only the two values the parse accepts. `type` and `partial` are tombstones and have no row.
1212

13-
The help text states what the runtime does with each value. `indexes[].fields` is free text, and no authoring door judges its names: not the schema parse, not the publish door, not `os validate`. A name that is not a stored column makes the SQL driver skip the whole index at sync with a warning in the server log, and the help text says exactly that. A field group has no field-name list: a field joins a group through its own `group` key.
13+
The help text states what the runtime does with each value. `indexes[].fields` is free text, and the schema parse, so a draft save, does not judge its names; `os validate`, `os build`, `os lint` and the publish door refuse a name that is not a field of the object (`object-field-ref-unknown`, #20479, in the same release). A name that is not a stored column, a `formula` field say, makes the SQL driver skip the whole index at sync with an error in the server log, and the help text says exactly that; `os migrate plan` reports the skipped index too (#20432, in the same release). A field group has no field-name list: a field joins a group through its own `group` key.
1414

1515
The two row schemas also carry a JSON Schema `title` on every property, as every repeater row schema must: `IndexSchema` on `name`, `fields` and `unique`, and `ObjectFieldGroupSchema` on its nine keys, the three deprecated aliases included. A property panel that reads the served schema's titles therefore shows a named column instead of a raw key. Each title is a `.meta({ title })` call and nothing more.
1616

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the `rest-*`, `analytics-*`, `view-*`, `package-*`, `object-*`, `sharing-*`, `audit-*`, `flow-*` and `http-*` migration entries states each lesson in words instead of citing tracker numbers
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries of the `rest-*` family (the retired OpenAPI 3.1 block, the
10+
endpoint `handlerStatus` marker, the REST server's dead config keys and the REST plugin
11+
durations renamed with their unit), the `analytics-*` family (the retired query envelope,
12+
the unknown-key refusals on cubes, and the closed date-range vocabulary and two-bound
13+
window), the `view-*` family (the filter value shaped by its operator and its array and
14+
absent-value refusals, the retired view-management protocol, the page-size default and the
15+
judged overlay `options` bag), the `package-*` family (the explicit all-tenants uninstall,
16+
the retired unmounted contract-map entries and rollback response, and the strict wrapped
17+
install body), the `object-*` family (the array `sort` on object blocks, the converged grid
18+
`data`, the rule-array `defaultFilters` and the index unknown-key refusal), the `sharing-*`
19+
family (the retired `SharingExecutionContext` type and the reconciled rule recipients), the
20+
`audit-*` family (the audit-log action values no writer produced), the `flow-*` family (the
21+
retry count, the decision-branch and edge refusals, first-match edge branching and blank
22+
predicate slots) and the `http-*` family (the retired error counter and server runtime
23+
vocabulary) are printed by `os migrate meta` as the header, `why:` and `verify:` lines of a
24+
manual change. Their text sent the reader to issue-tracker, decision-batch and ruling-record
25+
numbers — some of which no longer resolve, and some in another repository — for what a
26+
ruling, measurement or fix had decided; it now says what was decided, in the sentence being
27+
read. ADR ids are kept. Two entries of other families are corrected the same way:
28+
`api-error-retry-after-unit-in-key` now dates the population ruling its clause describes,
29+
and `inline-grid-column-currency-scale-refused` names its two currency rulings by date
30+
instead of by record number.
31+
32+
Text only: no entry id, `from` / `to`, conversion or matching logic changes, and the chain
33+
rewrites exactly what it rewrote before. One entry's `surface` (the header line of
34+
`flow-edge-condition-evaluated-slot-source-required`) drops the two tracker numbers it
35+
carried and names nothing else differently. The generated migration registry,
36+
`spec-changes.json` and the protocol upgrade guide carry the same text.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`liveness/state-counts.md` is replaced by `liveness/state-counts/<type>.md`: the generated liveness counts are one shard per governed type, and no total is committed anywhere.
6+
7+
Clause-②: no — no schema key moves, no accept set widens or narrows, no export changes. What changes is the layout of a generated table that ships in the tarball, and no count in it moves.
8+
9+
The ledgers ship inside this package (`files[]` includes `liveness`), so the changed tarball bytes are: `liveness/state-counts.md` removed, forty `liveness/state-counts/<type>.md` shards added (each carries exactly the row that file published for its type), and the prose in `liveness/README.md`, `liveness/book.json` and `liveness/translation.json` that named the removed file.
10+
11+
- **Where a count now lives.** A type's row is `liveness/state-counts/<type>.md`, byte-for-byte the row the single file carried. The table's total is not in any file: `pnpm --filter @objectstack/spec check:liveness` sums the shards when it reads them, prints the sum on its success line, and carries it in `--json` as `countsTotal`. At this release the sum is the total the removed file published: 940 live · 5 experimental · 1 live-elsewhere · 148 dead · 9 planned = 1103 classified.
12+
- **Why.** Every change that moved a liveness verdict rewrote the single file's total row, and GitHub's server-side merge runs no custom merge driver, so any two such changes in flight conflicted on that one line. With one file per type, changes that move different types touch different files.
13+
- **Anything that read `liveness/state-counts.md` from the published package** reads the shard for the type it wants, or sums the shards for the total. `gen:liveness-counts` rewrites only the shards whose counts moved and deletes the removed file if a merge brings it back; `check:liveness` fails while it is present.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
'@objectstack/driver-sql': minor
3+
'@objectstack/spec': patch
4+
'@objectstack/platform-objects': patch
5+
'@objectstack/lint': patch
6+
---
7+
8+
fix(driver-sql): a declared index that can never be built is logged at `error` and reported in drift
9+
10+
**Clause-②: yes (widening)**: the exported `DriftOp` union gains one member, `unbuildable_index`.
11+
No accept set changes. Nothing an author could write before is refused now.
12+
13+
A declared index names a column that no declaration will ever create when:
14+
15+
- the name is not a field of the object, for example a misspelling that the Studio save door
16+
admits (`os validate` / `os build` already refuse it); or
17+
- the name is a virtual `formula` field, which is computed on read and has no column. The same
18+
applies to a field-level `unique` on a formula field.
19+
20+
The SQL driver skips such an index at every sync. It used to say so at `warn`, and the drift
21+
report dropped the index from the expected set, so `os migrate plan` showed nothing. For a
22+
`unique` index, the declared constraint was not enforced and duplicate rows were accepted,
23+
while everything looked normal.
24+
25+
- **The sync logs the skip at `error`**, on the same durability channel as the duplicate-row
26+
refusals in the same loop. One line per skipped index per sync names the object, the index,
27+
each missing column with its reason (not a field of the object, or a formula field), and
28+
whether the index is `UNIQUE`. The structured meta carries `index`, `missing` and `unique`.
29+
- **Drift reports it** as a report-only entry: `kind: 'index_mismatch'`, `actual: '(absent)'`,
30+
`category: 'needs_confirm'`, `severity: 'error'` for a unique index and `'warning'` otherwise.
31+
Its op is the new member:
32+
33+
```ts
34+
{ type: 'unbuildable_index'; table: string; column?: string; indexName: string;
35+
unique: boolean; missingColumns: string[] }
36+
```
37+
38+
`missingColumns` lists only the columns that will never materialize. A declared column that
39+
is merely not added yet is pending additive work, not this finding.
40+
41+
**What a consumer that reads `op.type` now sees.** A new value, `'unbuildable_index'`. It has
42+
no reconciler arm, and none can exist, because there is no column to build over. The remedy is
43+
a metadata edit. It is in `INDEX_DRIFT_OPS`, so `isIndexDriftOp` answers `true` and it never
44+
triggers a SQLite table rebuild. `applyMigrationEntries` reports it `skipped` on every dialect.
45+
`os migrate plan` lists it under "Needs confirmation", addressed by its index name. `os migrate
46+
apply` counts it like any `needs_confirm` entry (so it asks for `--yes`), and then reports it
47+
skipped. The artifact-pinned boot warns about it and still starts, because
48+
only `destructive` entries refuse a boot. A `switch` over `op.type` that treats unknown values
49+
as "not applied" needs no change. An exhaustive `switch` with a `never` check gets one more case
50+
to handle.
51+
52+
**The object form's help text follows.** The `indexes` → Fields help in the Studio object form
53+
said the skip left "a warning in the server log". It now says an error, in English and in the
54+
zh-CN, ja-JP and es-ES translations. Nothing else in the text changes.
55+
56+
**The lint message follows too.** `object-field-ref-unknown`, on a misspelt `indexes[].fields`
57+
name, said the SQL driver skips the index "with only a warning, and drift drops it too". It now
58+
says the skip is logged at error and `os migrate plan` reports the index as unbuildable. The rule,
59+
its severity and its prescription are unchanged.
60+
61+
**Upgrade note:** on a database that already carries such an index, `os migrate plan` now
62+
reports one entry per index, and so does the boot's drift warning. That entry clears only when
63+
the metadata names stored fields or drops the index.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
"@objectstack/objectql": minor
3+
---
4+
5+
fix(objectql)!: a `date` string is written in its `YYYY-MM-DD` form, or it is refused with `VALIDATION_FAILED` / 400 (`invalid_date`) — `"2026/07/15"` is no longer stored verbatim as a non-day (#20481)
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) a refusal of a written VALUE at the record validator's date arm: no authorable key, spelling or stored shape of metadata moves, `packages/spec` is untouched, and a stored row keeps whatever it holds. What is refused is a date string without a leading YYYY-MM-DD, and which calendar day such a string meant (07/08/2026 names two) is not something a ledger entry can decide. The other categories are closed on facts: the package publishes (not `unpublished`); no ADR-0087 id covers a write-door value check (not `registered` / `already-registered`); and the change is runtime behaviour, not a TypeScript declaration (not `runtime-interface-only` / `type-surface-only`). -->
10+
11+
**BREAKING**: this narrows what a `date` field accepts as a written value. It ships as `minor` under the launch-window convention for accept-set narrowings (`check-changeset-no-major` refuses `major` until GA; the breaking-ness is carried by this banner and the ADR-0087 disposition above).
12+
13+
FROM a `date` field written as a string with no leading `YYYY-MM-DD` that `Date.parse` still reads — `"2026/07/15"`, `"07/15/2026"`, `"07/08/2026"`, `"15 July 2026"`, `"July 15, 2026"`, `"2026-7-15"`, `"2026.07.15"`, `"+002026-07-15"` → TO `VALIDATION_FAILED` / 400 with the field code `invalid_date` and its existing message, nothing written. The fix is one line: send `YYYY-MM-DD` (`"2026-07-15"`), or a JS `Date`.
14+
15+
No other spelling is read for you, on purpose: `07/08/2026` is July 8 in one locale and August 7 in another, and a guess stores the wrong day silently.
16+
17+
Measured through `POST /api/v1/data/:object` and a read-back, before this change, the process in America/New_York and PostgreSQL 16 at `DateStyle` `ISO, MDY`:
18+
19+
| written to a `date` | memory | SQLite | PostgreSQL | now, on all three |
20+
|:--|:--|:--|:--|:--|
21+
| `"2026/07/15"`, `"07/15/2026"`, `"15 July 2026"`, `"2026-7-15"`, `"2026.07.15"`, `"July 15, 2026"` | 201, read back verbatim | 201, read back verbatim | 201, `"2026-07-15"` | 400 `invalid_date` |
22+
| `"07/08/2026"` | 201, verbatim | 201, verbatim | 201, `"2026-07-08"` (a `DMY` server reads August 7) | 400 `invalid_date` |
23+
| `"+002026-07-15"` | 201, verbatim | 201, verbatim | 500 | 400 `invalid_date` |
24+
25+
A verbatim `"2026/07/15"` is not a day: it sorts and compares as text beside real days, so it falls out of every date range and every date filter. PostgreSQL's reading was its server's `DateStyle`, not the writer's.
26+
27+
What changes:
28+
29+
- The record validator's `date` arm asks one more question of a string: does the `date` storage rule read it? That rule (`@objectstack/core`'s `temporalStorageForm`) collapses a string with a leading `YYYY-MM-DD` to that day and hands every other string back unchanged. The question is asked through `isUninterpretableTemporalComparand`, the predicate the engine's temporal-comparand door already refuses such a `date` comparand with, so a `date` string refused on `where` is refused as a written value too. It applies on insert, update, a multi-row update and `engine.validate` (the dry run), before any driver write.
30+
31+
**Who is affected.** A caller that writes a `date` field as a locale or free-form string: a REST or SDK client, a flow, an MCP `create_record` / `update_record` call written by a model. The server import (`POST /api/v1/data/:object/import`) is not affected: it already turns a date cell into `YYYY-MM-DD` before the write. A row that already holds such a string keeps it, since nothing re-reads stored rows. An update that omits the field is not affected; one that sends the old string back is refused, so re-write the field as `YYYY-MM-DD`.
32+
33+
**Unchanged**, measured identical before and after on memory, SQLite and PostgreSQL through REST:
34+
35+
- a string with a leading `YYYY-MM-DD`, still stored as that day: `"2026-07-15"`, `"2026-07-15T10:00:00Z"`, `"2026-07-15 10:00"`, `" 2026-07-15"`;
36+
- a `Date`, still stored as its UTC calendar day;
37+
- an epoch-millisecond number, still refused with `invalid_date`;
38+
- a string `Date.parse` cannot read, still refused: `"20260715"`, `"15/07/2026"`;
39+
- the year range 0001..9999;
40+
- every `datetime` and `time` value.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
fix(platform-objects): the ja-JP and es-ES platform-object bundles no longer ship English labels, options and help as copies of the source (#20493)
6+
7+
Clause-②: no
8+
9+
A ja-JP or es-ES console showed English on Setup surfaces: the Invite user
10+
dialog listed the membership roles with **Delegated Admin** among the
11+
translated ones. The keys were present in `ja-JP.objects.generated.ts` and
12+
`es-ES.objects.generated.ts`, but their values were byte copies of the English
13+
source that `os i18n extract --fill=default` seeds.
14+
15+
- ja-JP: 340 of the 383 string leaves that equalled their `en` source are now
16+
translated: labels, plural labels, select options, help, descriptions and
17+
empty states. `delegated_admin` reads 委任管理者 on both `sys_member` and
18+
`sys_invitation`, the word the console already uses for that role.
19+
- es-ES: 338 of the 392 are now translated the same way. `delegated_admin`
20+
reads Administrador delegado on both objects, again the console's word.
21+
- The rest stay as written by design, each recorded with its reason in a
22+
per-locale ledger (`objects-ja-jp-echo-decisions.test.ts`,
23+
`objects-es-es-echo-decisions.test.ts`). ja-JP keeps 43: sign-in provider
24+
brands, the bare `ID` initialism, protocol names (JWKS, IdP SSO URL,
25+
Message-ID, UI / API), the `Web` client type as the bundle already renders it,
26+
the Cc / Bcc header abbreviations, and placeholders that show a value the
27+
admin types. es-ES keeps 54: the same brands, `ID`, JWKS, Message-ID,
28+
UI / API, `Web` and placeholders, plus words Spanish spells as English (Error,
29+
Actor, Global, Variables), the loanwords the bundle already uses (Token,
30+
Checksum, Slug) and Cc.
31+
32+
`ja-JP.source-hashes.generated.ts` and `es-ES.source-hashes.generated.ts` were
33+
regenerated by `pnpm i18n:extract`: each records a leaf only while the leaf is
34+
a copy of its source, so they now list exactly those 43 and 54. zh-CN is
35+
unchanged.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Provenance comments in `data/` were re-anchored
6+
7+
Comment and docblock lines under `src/data` (all but the files other open work
8+
holds) that cited tracker numbers which no longer resolve on GitHub now cite
9+
the commit in this repository's history that decided the matter, and say in
10+
their own words what was decided. Comments only: no type, schema, export or
11+
runtime behaviour changes.

0 commit comments

Comments
 (0)