Skip to content

Commit f84cd82

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-22110-flow-text-slot-double-brace
2 parents bff973d + ca135dc commit f84cd82

325 files changed

Lines changed: 15888 additions & 3249 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.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/plugin-auth": patch
3+
---
4+
5+
The permission-set grant readers in `@objectstack/plugin-auth` read which set a grant holds from its name column, `sys_user_permission_set.permission_set` (ADR-0131 D4)
6+
7+
Clause-②: no
8+
9+
- **What reads the name now.** The last-administrator guard (`registerLastAdminGuard`) counts a grant-anchored platform administrator from an unscoped, in-window grant whose `permission_set` is `admin_full_access`, provided the `admin_full_access` row of that name is active. The default-organization bootstrap (`ensureDefaultOrganization`) finds the platform administrator by the same grant name. The self-registration grant checks by name whether the new user already holds the declared set. None of them reads `permission_set_id` for this any more.
10+
- **The guard treats `permission_set` as a standing column.** A write that clears the name on the last administrator's grant is refused like any other revocation. A write that re-points `permission_set_id` without a name is treated as taking the standing away, because the platform derives the new name only after the guard runs. A write that only repeats the grant's own id keeps the standing.
11+
- **A grant whose name is empty.** A grant written before the name column existed has no name until `@objectstack/plugin-security`'s one-time backfill names it at `kernel:bootstrapped`. The guard does not count such a grant as an administrator. When no administrator is counted, such a grant, if unscoped and in-window, is evidence that the environment is not a fresh install, so the write is refused instead of the bootstrap window opening. The default-organization bootstrap answers `no_admin` for it and binds no owner; that answer records no decision, so a later trigger binds once the grant has its name.
12+
- **Nothing to migrate.**
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/plugin-security": patch
3+
---
4+
5+
The permission-set grant readers in `@objectstack/plugin-security` read which set a grant holds from its name column, `sys_user_permission_set.permission_set` (ADR-0131 D4)
6+
7+
Clause-②: no
8+
9+
- **What reads the name now.** The explain engine's dropped-grant provenance (`buildContextForUser`: an expired grant, or a grant of a deactivated set), the platform-admin bootstrap's existing-holder check (`bootstrapPlatformAdmin`, and the seed-ownership claim's `findExistingPlatformAdmin`), the organization-admin reconcile (`reconcileOrgAdminGrant`, `backfillOrgAdminGrants`) and the delegated-administration gate's judgement of a stored grant it is asked to change or delete. Each reads the grant's `permission_set` instead of its `permission_set_id`. Deactivation is still read from the `sys_permission_set` row, now found by that name: the grant's own organization's row, else the organization-less one. The authorization resolver in `@objectstack/core` still reads the id, and nobody's resolved permissions change.
10+
- **A grant whose name is empty.** A grant written before the name column existed has no name until the one-time backfill names it at `kernel:bootstrapped`, and the backfill leaves a grant unnamed when its id names no set row or another organization's set row. Such a grant grants nothing through these readers. Explain reports nothing for it. The organization-admin reconcile still finds it through its id when it revokes the grant or checks for a duplicate before inserting one. The delegated-administration gate refuses a delegate's change to it; a tenant administrator is not affected. The platform-admin bootstrap does not promote a second administrator while an unscoped grant on the `admin_full_access` row is still unnamed. It returns `reason: 'admin_grant_unnamed'` without an `adminUserId`, logs a warning, and the next boot reads the grant by its name.
11+
- **Within the organization-admin reconcile,** a pair that already holds the organization-admin set by name, through any organization's copy of it, gets no second grant.
12+
- The earlier release notes for the name column said no reader used it yet. That is no longer true of the readers listed above.
13+
- **Nothing to migrate.**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/metadata-core': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/metadata': minor
5+
'@objectstack/platform-objects': minor
6+
'@objectstack/spec': minor
7+
---
8+
9+
feat(metadata-core,metadata-protocol,metadata,platform-objects,spec)!: `sys_view_definition` retires as inert (ADR-0131 D13)
10+
11+
Clause-②: no (narrowing)
12+
13+
<!-- adr-0087: registered sys-view-definition-retired -->
14+
15+
**BREAKING**, graded `minor` on the v18 prerelease line: Changesets is in pre mode with the tag `next`, and the fixed group is already majored by the line's opening marker, so this ships in an `18.0.0-next.N` like every other stage.
16+
17+
`sys_view_definition` was declared for runtime-authored shared and personal views (ADR-0017) and never used for them: no framework code writes or reads its rows, and the Studio console's view doors write the ADR-0005 `view` overlay in `sys_metadata` through the metadata API. ADR-0131 D13 retires it. A runtime-authored view is a `view` metadata item, written through `PUT /api/v1/meta/view/<name>` (the client's `meta.saveItem` for type `view`); nothing about that path changes.
18+
19+
**What moves for consumers.**
20+
21+
- **The object.** FROM `import { SysViewDefinitionObject } from '@objectstack/metadata-core'` (or from `@objectstack/platform-objects`, or its `/metadata` subpath) TO nothing: delete the import. Neither `MetadataPlugin` nor the metadata protocol assembly registers the object any more, so the generic data door no longer serves it.
22+
- **The migration exports** of `@objectstack/metadata-protocol`. FROM `ensureViewDefinitionActiveIndex`, `resolveIndexExec`, `buildActiveIndexSql`, `VIEW_DEFINITION_TABLE`, `VIEW_ACTIVE_INDEX_NAME`, `VIEW_ACTIVE_PROBE_INDEX_NAME`, `VIEW_ACTIVE_INDEX_COLUMNS`, `EnsureViewIndexLogger`, `EnsureViewIndexStatus` and `EnsureViewIndexResult` TO nothing: delete the import. `classifyIndexFailure` and the `IndexExec` type are still exported, unchanged, from the shared index-migration module.
23+
- **The name.** `PLATFORM_OBJECTS_BY_PACKAGE['metadata-core']` (`@objectstack/spec/system`) lists four objects, and `isPlatformProvidedObjectName` answers `false` for `sys_view_definition`. A stack that names it (a lookup target, a flow trigger, a permission entry, a platform-global declaration) is now flagged as naming an unknown object: remove the reference.
24+
- **`os migrate duplicates`.** `runtimeIndexPreflight` carries three entries (the two `sys_metadata` overlay indexes and `sys_setting`'s row identity), with no `idx_sys_view_def_active` entry. A serving boot no longer runs the active-row index migration for the table, and no longer logs its `sys_view_definition active-row index` lines.
25+
- **Translations.** The platform translation bundles no longer carry `sys_view_definition` keys.
26+
27+
**Existing databases.** Schema sync is additive and never drops a table, so a database an earlier release provisioned keeps `sys_view_definition`, with any rows a caller wrote through the generic data door; nothing reads them. `os migrate apply --allow-destructive` does not drop it either, because it reconciles declared objects only (measured on one database: it dropped an orphaned column of a declared table and left this table and its row in place). On a project with a host config, `os migrate plan` lists it among the platform-prefixed tables nothing declares. Export any row worth keeping; dropping the table is the operator's call, by hand: `DROP TABLE sys_view_definition`.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The form view `submitBehavior` property help no longer ends in a ruling date (#22093)
6+
7+
Clause-②: no
8+
9+
Studio, the reference docs and the published `objectstack-ui` skill show this describe as the property help. It still says that on the `redirect` arm `url` is relative-only, interpolates only declared record fields as `{{record.field_name}}` and is URL-escaped; the "(ruled 2026-08-11)" parenthetical is gone, and the ruling's date and tracker reference live in the code comment above the describe. Wording only: no key, type, accept/reject or export change. The `react-blocks.md` row and the `view` / `protocol` reference pages are regenerated from it.

‎.changeset/22135-security-catalog-one-holder.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ The platform's own permission sets (`admin_full_access`, `member_default`, …)
2222

2323
**What an author sees.** The boot, or the install, fails with an ADR-0112 envelope: `code: 'NAMESPACE_CONFLICT'` (the code the namespace gate already carries; `NAMESPACE_CONFLICT_CODE` is exported) and `status: 422`. The message names the incoming package and the existing holder of each conflicting name, all conflicts in one message. The thrown error carries `conflicts[]` with `{ catalogType, name, incomingPackageId, existingHolder }`, where `existingHolder` is `{ kind: 'package', packageId }`, `{ kind: 'environment' }` or `{ kind: 'built-in' }`. **The one-line fix: rename the item in one of the two packages, or uninstall one of them.** A built-in name is never available to a package. An assignment that named the old name must name the new one; nothing rewrites stored assignments.
2424

25-
**What is NOT refused.** The same package registering its own name again (an idempotent reload, a re-install, a hot reload). An item of any other metadata type shared by two packages. An environment save over a package-held name: a registration with no package (every `sys_metadata` hydration and metadata write-through) is never judged here, and a packaged permission set is already locked against an in-place edit (`403`). `OS_METADATA_COLLISION=warn` downgrades the namespace gate only. It does not downgrade this refusal. At cold boot, packages register before the environment catalog loads from `sys_metadata`, so a package newly added to a deployment over a permission-set or position name the environment catalog already holds is not refused at cold boot (the registry's existing collision warning fires), while a hot install of the same package is refused; a cold-boot refusal is ruled and tracked on #22307, which lands separately.
25+
**What is NOT refused.** The same package registering its own name again (an idempotent reload, a re-install, a hot reload). An item of any other metadata type shared by two packages. An environment save over a package-held name: a registration with no package (every `sys_metadata` hydration and metadata write-through) is never judged here, and a packaged permission set is already locked against an in-place edit (`403`). `OS_METADATA_COLLISION=warn` downgrades the namespace gate only. It does not downgrade this refusal. At cold boot, packages register before the environment catalog loads from `sys_metadata`, so this door cannot see an environment-held name then; the engine checks every package-held position and permission-set name against the environment catalog right after it loads, and refuses the boot with this envelope (the cold-boot entry in this release).
2626

2727
**Measured producers.** On objectstack `1604e094f5`, the four examples (`app-crm`, `app-showcase`, `app-todo`, `app-multi-package`) and the platform built-ins carry 50 catalog declarations, and no name has more than one holder. Deployed and marketplace packages NOT MEASURED.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"@objectstack/lint": minor
3+
"@objectstack/metadata-protocol": minor
4+
---
5+
6+
fix(lint)!: `os build` and the object save door refuse a select option's `visibleWhen` that reads `parent`, or any other root the server's option check does not bind (#22157)
7+
8+
Clause-②: no (narrowing: `os build` and the object save door refuse a select option's `visibleWhen` that reads `parent`, which the runtime's option check cannot bind)
9+
10+
A select option's `visibleWhen` is a gate the server enforces on write: the rule validator evaluates the predicate for the value a caller picks, and refuses the value when the predicate is false. That option check binds `record`, `previous` and the acting user (`current_user`, and its ADR-0068 aliases `user`, `ctx` and `os`), and nothing else. An option predicate that read `parent`, such as `parent.status == 'closed'`, still passed `os build` and the object save door. `parent` is a root the platform declares, and a field's own `readonlyWhen` and `requiredWhen` bind it on an object with exactly one `master_detail`. The option check does not bind it. So the predicate faulted on every write that picked the option, the server logged "the option's gate was NOT enforced on this write", and the value was admitted.
11+
12+
The build's expression rule (`validateStackExpressions`) now gives a select option's `visibleWhen` a root verdict over the roots the option check binds. A predicate that reads any other root the platform declares, `parent` above all, is refused at `error` and located at the option (`object 'NAME' · field 'FIELD' option 'VALUE' visibleWhen`). The message names the option, the field and the root. The object save door runs the same pass, so its verdict is the build's finding: the same rule id (`expression-invalid`), location, message and hint.
13+
14+
**BREAKING — what moves for consumers.**
15+
16+
- `os build`, `os validate` and `os lint` refuse an option `visibleWhen` that reads a root other than `record`, `previous`, `current_user`, `user`, `ctx` or `os`. Besides `parent`, that covers the roots the platform declares for other evaluation sites, such as `input`, `vars`, `trigger`, `data` and `features`. Each of them faulted at the option check in the same way.
17+
- An object write in publish mode that carries such an option answered 200. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that option. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`).
18+
19+
**Remedy.** Rewrite the predicate against what the option check binds: `record.FIELD`, `previous.FIELD`, or the acting user as `current_user`. To gate a choice on a master-detail header's state, read a column the detail object declares, and denormalise the header value onto the detail; `parent` is bound only for a field's own `readonlyWhen` and `requiredWhen`. Saving the object as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged.
20+
21+
**Unchanged.**
22+
23+
- The server's option check is unchanged. It binds what it bound before, and an option predicate that faults is still logged and admitted. If the runtime comes to bind `parent` for an option, this refusal is lifted in that same change.
24+
- The acting user is still accepted in an option's `visibleWhen` under all four ADR-0068 spellings, and so is a grant check such as `current_user.can('OBJECT', 'edit')`. On a field's own `requiredWhen`, `readonlyWhen` or `visibleWhen`, the acting user is still refused and `parent` is still accepted, as before.
25+
- A root the platform does not declare at all, such as `app`, was already refused in an option's `visibleWhen` by the bare-reference check, and it still is, with the same message.
26+
- Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved, and that save is judged.
27+
- `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write.
28+
- Measured before crossing: every object this repository ships carries 5 option predicates, all on `showcase_cascade`: four `record.country` cascades and one `current_user.positions` role gate. That is over the 119 objects from its `*.object.ts` files and the two `app-multi-package` sub-stacks, and over the example stacks as `defineStack` composes them (33 objects). They have 0 refusals and 0 advisories, at the build and at the door, before this change and after it.
29+
- No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`.
30+
31+
<!-- adr-0087: not-required (no-migration-prescription) a refusal at `os build` and at the object save door of a select option's visibleWhen predicate that reads a root the server's option check does not bind: no authorable key, spelling, export or stored shape moves, and no stored row is read, rewritten or converted. A stored object whose option predicate is refused keeps loading until it is next saved, and the repair is the author's rewrite of the predicate against a bound root, which no ledger entry can derive. The other categories are closed on facts: the packages publish (not unpublished); no ADR-0087 id covers this verdict (not already-registered); and the change is a build and door verdict, not a declaration (not runtime-interface-only or type-surface-only). -->

0 commit comments

Comments
 (0)