Skip to content

Commit 70963fb

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24579-v18-sweep-round6-pin-coverage
2 parents 6940b37 + 045dae8 commit 70963fb

39 files changed

Lines changed: 1331 additions & 214 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/platform-objects': minor
3+
---
4+
5+
fix(platform-objects)!: `sys_organization` no longer declares `change_slug`, whose door only the cloud orchestrator mounts (#24318)
6+
7+
Clause-②: no (narrowing) (a platform object stops declaring an action whose door exists only in cloud; owes a `CONTRACT_REVIEW_TIER` review on the narrowing face before the queue, and the `repo:cloud#2` seat's pointer before landing)
8+
9+
`sys_organization` declared a `change_slug` action ("Change Slug") at `record_header`, gated on `multiOrgEnabled`, whose target, `/api/v1/cloud/organizations/{id}/change-slug`, no package in this repository serves. So on every open-source composition with multi-organization on, the organization record page offered Change Slug in its `⋯` overflow, and the click answered `404 ENDPOINT_NOT_FOUND`. Measured on a booted multi-organization showcase.
10+
11+
The action now belongs to the composition that serves its route. That composition declares `change_slug` as a top-level action bound by `objectName: 'sys_organization'`, and the registry attaches such an action to the served object, whichever package registers first.
12+
13+
**BREAKING — what moves for consumers.** A declared action leaves a published platform object, shipped as `minor` under the launch-window convention for breaking changes (pre mode was exited at `c46b5f14cf`, so the launch-window guard is armed and a breaking change ships as `minor`).
14+
15+
- FROM: `GET /api/v1/meta/object/sys_organization` served `change_slug` on every composition, and the four generated locale bundles carried its `label`, `description` and `successMessage`.
16+
- TO: the object serves it only where a composition binds it. The open-source record header offers Invite User, Set Active and Rename Organization inline, and Leave Organization in the overflow; each of them is a better-auth door this composition mounts.
17+
- Fix: cloud declares its own `change_slug`, the same declaration bound by `objectName: 'sys_organization'`, with its own translations. An open-source composition changes an organization's slug through `update_organization` (better-auth's `organization/update`, list row), as before.
18+
19+
**Unchanged.** `rename_organization` (name and logo, no slug) and `update_organization` (name, slug and logo, list row only) keep their declarations, gates and order. No stored row moves.
20+
21+
<!-- adr-0087: not-required (no-migration-prescription) a platform object stops declaring one action whose door exists only in another composition: no authorable spec key, spelling, export or stored shape moves, and no stored row is read, rewritten or converted. The repair is the serving composition's own declaration of the action, which no ledger entry can derive. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this declaration (not already-registered); and the change is an object's declared action, not a runtime interface, a type surface or an export (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec)!: `PUBLIC_AUTH_FEATURES.multiOrgEnabled.gatedInputs` no longer lists `sys_organization.actions.change_slug` (#24318)
6+
7+
Clause-②: no (narrowing) (a platform object stops declaring an action whose door exists only in cloud; owes a `CONTRACT_REVIEW_TIER` review on the narrowing face before the queue, and the `repo:cloud#2` seat's pointer before landing)
8+
9+
`gatedInputs` is the census of platform-object inputs gated on a public auth feature, which the platform objects' completeness guard holds equal to their declarations in both directions. `sys_organization` no longer declares `change_slug` (its door is the cloud orchestrator's, and the composition that serves it declares the action), so its row leaves the census.
10+
11+
**BREAKING — what moves for consumers.** One string leaves a published constant, shipped as `minor` under the launch-window convention for breaking changes.
12+
13+
- FROM: `PUBLIC_AUTH_FEATURES.multiOrgEnabled.gatedInputs` listed seven `sys_organization` actions, `'sys_organization.actions.change_slug'` among them.
14+
- TO: it lists six: `create_organization`, `update_organization`, `rename_organization`, `delete_organization`, `set_active_organization` and `leave_organization`.
15+
- Fix: none for an action declared outside the platform objects. The census is not a gate on other packages: a top-level action bound to `sys_organization` that declares `requiresFeature: 'multiOrgEnabled'` is lowered to the same `features.multiOrgEnabled != false` predicate and served with it, measured on a booted composition.
16+
17+
**Unchanged.** The `multiOrgEnabled` key, its surface (`crud`) and its semantics (`default-on`); every other feature's row. The constant is declared `as const`, so its literal type loses the one tuple member too.
18+
19+
<!-- adr-0087: not-required (no-migration-prescription) one path string leaves a published census constant that only the platform objects' own completeness guard reads: no authorable spec key, spelling, export or stored shape moves, and no stored row is read, rewritten or converted. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this row (not already-registered); and the export stays, its declared entry type unchanged; the `as const` literal tuple loses one member, which nothing in this repository names (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
---
4+
5+
fix(plugin-audit): an object definition changed through the metadata door takes effect in the audit writers at once, with no restart
6+
7+
Clause-②: no
8+
9+
The audit writers kept each object's first definition for the life of the process. They now read the registered definition on every call. The registry already holds the merged definition behind its own invalidation points, so the read is two map lookups.
10+
11+
- **Attachments follow `enable.files`.** An object whose `enable.files` was switched on through `PUT /api/v1/meta/object/:name` kept refusing `sys_attachment` writes with 403 `FILES_DISABLED` until the server restarted, although the metadata read-back already showed `enable.files: true`. The same attachment is now admitted at once, and switching the flag off refuses it again. `enable.feeds` on comments follows the same read.
12+
- **The ledger copy follows field changes.** A `password` field added through the door to an already-audited object is masked on the ledger copy from the moment it is added, as on every other read exit. The same read decides the mask for `secret` fields and the omission of `internal` ones.
13+
- **The activity row follows the object.** `enable.activities`, `activityMilestones`, the title field and the object label are read from the current definition too.
14+
15+
What the metadata door accepts is unchanged.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): two hand-built 400 refusals now put an ADR-0114 `FieldErrorCode` catalog member on `details.fields[].code` instead of a Zod issue code (#24597)
6+
7+
Clause-②: no
8+
9+
`FieldErrorCode` is a closed catalog: a service may not invent a field code. Two refusals built their `details.fields[]` rows by hand and wrote Zod's issue code there instead of a catalog member:
10+
11+
- **The run-lifecycle body refusal** on `POST /api/v1/automation/:name/runs/:runId/cancel` and `POST /api/v1/automation/:name/runs/:runId/restore-suspension`. For an unknown body key, the row's `code` changes from `unrecognized_keys` to `unknown_field`. `unknown_field` is the code the `resume` door on the same run already answers for an unknown key.
12+
- **The permission-set clone door's name rule** on `POST /api/v1/security/permission-sets/:name/clone`. For a `name` that is not lowercase snake_case, the row's `code` changes from `invalid_string` to `invalid_format`.
13+
14+
What changes on the wire: a client that switched on `details.fields[].code` sees `unknown_field` where it saw `unrecognized_keys`, and `invalid_format` where it saw `invalid_string`. Both are members the published catalog already declares. Nothing else changes: each refusal keeps its status (400), its `error.code` (`VALIDATION_FAILED`), its message and its rows' `field` and `message`. No body is accepted or refused that was not before, and no catalog member is added.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): the boot report's unnamed-grant line says what 18.0 supports for a v17 database, not a command 18.0 refuses
6+
7+
Clause-②: no
8+
9+
- **The error line for `sys_user_permission_set` grants whose `permission_set` is empty** used to end with a fix: plan with `os migrate organization-ownership --out plan.json`, then run `os migrate organization-ownership --apply --backup-taken --plan-file plan.json`, restart, with a warning that granting the set again by name before it runs leaves a second grant for the ceremony to report. 18.0 refuses that command on every database, so the operator was sent to a refusal. The line now ends with the sentence the drop-gate refusal of `os migrate apply --allow-destructive` carries: "18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path."
10+
- **Unchanged:** the count, the "permission_set is empty" fact, the ADR-0131 D4 reason ("each confers nothing"), "Nothing was changed.", the structured meta, the log level and the report's result. Such a grant still confers nothing and is still listed.
11+
- **Removed from the line:** the two command steps and the restart step, and the advice about granting the set again by name. They described the 18.x upgrade ceremony, which 18.0 does not carry.

‎.claude/skills/pm-dispatch/references/dispatch-runbook.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@
134134
- 该答案与从不存在不可区分,⛔ 永不读作死亡信号。
135135
- 跨账号接班活性只认 GitHub 产出读数,draft-PR 时点交报正为此存在。
136136

137-
## subagent 批派发前置:先快进本地检出
137+
## subagent 批派发前置:先把本地检出钉到 main
138138

139-
- subagent 在 PM 容器内运行,agent 定义与技能文本读本地检出,不是 `origin/main`。
140-
- 派发 subagent 批之前先快进:`git -C <repo-root> pull --ff-only origin main`。
141-
- 检出过期则整批拿到过期定义;用 `--ff-only` 让脏或分叉的检出大声失败,⛔ 不静默合并。
139+
- subagent 读 PM 容器本地检出的 agent 定义与技能文本,不读 `origin/main`:过期则整批过期。
140+
- 每批派发前:`git -C <repo-root> fetch origin main && git -C <repo-root> checkout --detach origin/main`。
141+
- 游离 HEAD 使停止钩子跳过分支块,⛔ 不快进会话分支;只拒与切换冲突的脏改动,不拒分叉。
142142

143143
## 接手中断的 dev(worktree 接手协议)
144144

‎content/docs/concepts/metadata-lifecycle.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ later ships. See [ADR-0070](https://github.com/objectstack-ai/objectstack/blob/m
106106

107107
**Most metadata types must not be overlaid by an environment** — overlaying a packaged item would break the physical schema or the behaviour its package ships. The whitelist lives in **one** place: `MetadataTypeRegistryEntry.allowEnvironmentOverlay` in `packages/spec/src/kernel/metadata-plugin.zod.ts` (spelled `allowOrgOverride` before protocol 18; `GET /api/v1/meta/types` serves it per entry as `allowEnvironmentOverlay`).
108108

109-
> **ADR-0131 D6 — the per-organization overlay axis is retired.** The `/meta` doors (REST and the runtime dispatcher) carry no organization into a metadata write or read: a Studio or `PUT /api/v1/meta/...` write of the five ✅ types below lands **environment-wide** (since ADR-0131 D7 the `sys_metadata` family declares no organization column) and is served to every organization, and an organization admin holding only the retired `manage_org_presentation` capability is refused (`403`). The protocol itself refuses every organization-scoped write, and every read — item, list, layers, cached, history, diff, audit, drafts and the commit timeline — is environment → code, whatever organization a caller names. An overlay row an earlier release stored under an organization — under the `single` posture, every Studio save of these types, filed under the Default Organization — is served by no read, and an 18.x `os serve` refuses to start on a database still holding one until the v18 migration ceremony (ADR-0131 C7: `os migrate organization-ownership --out PLAN`, then `--apply --plan-file PLAN`) has carried it to the environment layer and dropped the column; nothing deletes or rewrites it before then. The anonymous form doors read the environment alone too: the ceremony carries every organization-layer public-form withdrawal onto the environment, fail-closed. The table still records which types take an overlay of a shipped item.
109+
> **ADR-0131 D6 — the per-organization overlay axis is retired.** The `/meta` doors (REST and the runtime dispatcher) carry no organization into a metadata write or read: a Studio or `PUT /api/v1/meta/...` write of the five ✅ types below lands **environment-wide** (since ADR-0131 D7 the `sys_metadata` family declares no organization column) and is served to every organization, and an organization admin holding only the retired `manage_org_presentation` capability is refused (`403`). The protocol itself refuses every organization-scoped write, and every read — item, list, layers, cached, history, diff, audit, drafts and the commit timeline — is environment → code, whatever organization a caller names. An overlay row an earlier release stored under an organization — under the `single` posture, every Studio save of these types, filed under the Default Organization — is served by no read, and on a SQL database (SQLite, PostgreSQL, MySQL) still holding one a v18 `os serve` refuses to start. 18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path. In that release, the v18 migration ceremony (ADR-0131 C7: `os migrate organization-ownership --out PLAN`, then `--apply --plan-file PLAN`) carries such a row to the environment layer and drops the column; nothing deletes or rewrites it before then. The anonymous form doors read the environment alone too: in that release the ceremony carries every organization-layer public-form withdrawal onto the environment, fail-closed. The table still records which types take an overlay of a shipped item.
110110

111111
| Type | `allowEnvironmentOverlay` | Rationale |
112112
| :--- | :---: | :--- |

‎content/docs/deployment/cli.mdx‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,31 +196,40 @@ os dev --database file:./data/test.db --auth-secret $(openssl rand -hex 32)
196196
| `--seed-admin` / `--no-seed-admin` | — | Seed a dev admin (`admin@objectos.ai` / `admin123`) on an empty DB — default on; override with `--admin-email` / `--admin-password` |
197197
| `-v, --verbose` | — | Verbose output |
198198

199-
##### A 17.x development database is upgraded in place
199+
##### A 17.x development database
200200

201201
A v18 server refuses to start on a database still in the v17
202202
organization-ownership shape until the [ownership ceremony](#os-migrate-organization-ownership)
203-
has run on it. `os dev` runs that ceremony for you when the database is a local
204-
SQLite file:
203+
has run on it.
205204

206205
<Callout type="warn">
207206
18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 the ceremony refuses, so `os dev` stops at step 2 with its
208207
refusal and changes nothing.
208+
18.0 does not upgrade a v17 database: keep a v17 database on a 17.x release
209+
until the 18.x release that carries the upgrade path. For a local SQLite file,
210+
`os dev` in 18.0 still takes step 1's copy first and prints where it is,
211+
beside the ceremony's `NOT_IMPLEMENTED` refusal; any other database gets no
212+
copy and the same answer.
209213
</Callout>
210214

215+
In the 18.x release that carries the upgrade path, `os dev` runs that ceremony
216+
for you when the database is a local SQLite file:
217+
211218
1. It copies the database file and its `telemetry` sibling (each with its
212219
`-wal` / `-shm`) into `<data dir>/backups/organization-ownership-<timestamp>/`.
213220
2. It runs `os migrate organization-ownership --dev` (the plan), then applies
214221
that plan. The plan, the apply report and the ceremony's log are kept
215222
beside the copy.
216223
3. It prints the ceremony's warnings and starts the server again.
217224

218-
If a step blocks, or the post-check still holds rows (a row whose owner cannot
225+
In that release, if a step blocks, or the post-check still holds rows (a row whose owner cannot
219226
be derived, named per table with its remedy), `os dev` stops and prints what
220227
holds it, where the copy is, and the command that resumes the run. It tries once per session. A database that is
221228
not a local SQLite file (PostgreSQL, a remote libSQL, `:memory:`) is not
222-
upgraded in place: run the ceremony yourself. `os serve` and `os start` never run
223-
it; they refuse and name the command.
229+
upgraded in place: run the ceremony yourself.
230+
231+
`os serve` and `os start` never run the ceremony: they refuse to start, and on
232+
a v17 database their 18.0 refusal says that 18.0 does not upgrade it.
224233

225234
##### Serving dev over https
226235

@@ -1025,13 +1034,18 @@ safe. No flag skips these refusals:
10251034
- `sys_account.issuer` is dropped only when its retirement pre-flight reads the
10261035
table clean (`os migrate account-issuer` reports the colliding rows).
10271036
- `sys_comment.reactions` waits for a verified `os migrate comment-reactions --apply` run.
1028-
- `organization_id` on any table the organization-ownership ceremony covers
1029-
waits for a verified `os migrate organization-ownership --apply --plan-file FILE`
1030-
run. These tables are the ADR-0131 D7 tables and the `sys_metadata` family.
1031-
The ceremony drops those columns itself, in its column-drops step, before it
1032-
records the run verified. Dropping one first would turn every legacy
1037+
- `organization_id` waits for a verified run of the organization-ownership
1038+
ceremony, on every table the drift offers to drop it from: the ceremony plans
1039+
every table that still carries the column, the ADR-0131 D7 tables and the
1040+
`sys_metadata` family among them. Dropping one first would turn every legacy
10331041
organization row into an environment row. Under `--json` the refusal answers
10341042
`message: 'refused_organization_ownership_unmigrated'` and lists the `tables`.
1043+
18.0 itself never records a verified run, so on a v17 database this refusal
1044+
holds on every run. 18.0 supports fresh installs only and does not upgrade a
1045+
v17 database: keep a v17 database on a 17.x release until the 18.x release
1046+
that carries the upgrade path. In that release, the ceremony's
1047+
`os migrate organization-ownership --apply --plan-file FILE` run drops these
1048+
columns itself, in its column-drops step, before it records the run verified.
10351049
10361050
Each refusal holds the whole `--allow-destructive` run: nothing in it is
10371051
created, applied or dropped, the run's other changes included. A run without
@@ -1478,14 +1492,15 @@ reports the failed read and exits `1`. A failed read or write exits `1`.
14781492
14791493
The v18 ownership ceremony (ADR-0131 D10). A v18 `os serve` / `os start` refuses
14801494
to start on a database still in the v17 organization-ownership shape until this
1481-
has run. Under `os dev` it runs on its own for a local SQLite database (see
1482-
[`os dev`](#os-dev)).
1495+
has run. In the 18.x release that carries the upgrade path, `os dev` runs it on
1496+
its own for a local SQLite database (see [`os dev`](#os-dev)).
14831497
14841498
<Callout type="warn">
14851499
18.0 supports fresh `single` installs; upgrades and walled postures arrive in 18.x. In 18.0 this command refuses, plan and apply alike: it exits `1` with
14861500
`NOT_IMPLEMENTED` (under `--json`, `code` beside `error`) and reads and
14871501
writes nothing. Keep a v17 database on a 17.x release until the 18.x release
1488-
that carries the upgrade path.
1502+
that carries the upgrade path. The invocations below, and what the list after
1503+
them says the ceremony does, are that 18.x release's.
14891504
</Callout>
14901505
14911506
```bash

0 commit comments

Comments
 (0)