Repository navigation
Commit 1e9f72d
fix(cli): os migrate organization-ownership --apply carries a public-form withdrawal onto a read-only package view into its .custom pair, declared (#29508)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9BYq3eGXavUMZr5cccFBPca)_
Fixes #29396
Clause-②: no
Metadata-protocol: none
Measured on the real command (table under Why): the cli now completes a
step it used to stop on. No door, close predicate or accepted request
shape changes; the diff touches only `packages/cli`.
## 维护者速读
- **改了什么**:`os migrate organization-ownership --apply`
把组织撤回的公开表单带到"只读代码包"的视图上时,不再停下报阻塞,而是先确保该包的 `.custom` 配对包存在,再把撤回存进配对包,并声明
`overrides: { package: 基础包, basedOn: 基础包当前版本 }`。
-
**为什么改**:只读基础包本身不再接受定制(上一轮关闭了以基础包名义的写入),仪式因此停在阻塞上,操作员要手工把撤回存进配对包再重跑。现在仪式自己完成这一步。
-
**风险与回滚**:可写包的视图照旧原地保存。配对包确保不了、或门拒绝写入配对包时,仍走原来以包名义的保存:门放行就照旧带过去,门拒绝就照旧阻塞,阻塞文案多带一句配对包的拒绝原因。没有改任何门、错误码或元数据格式。回滚
= revert 本 PR。
- **先看**:`packages/cli/src/utils/organization-ownership-apply.ts` 的
`carryIntoPair` 和 `carryFormWithdrawals`
里新加的分支;`packages/cli/src/commands/migrate/organization-ownership.ts` 新的
`pairs:`
接线;`packages/cli/src/commands/migrate/organization-ownership.withdrawal-pair.integration.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/cli exec vitest run --project
integration
src/commands/migrate/organization-ownership.withdrawal-pair.integration.test.ts`,预期
1 个通过:撤回进了 `com.example.wdpair.custom`,声明了 `basedOn:
1.4.0`,重新启动后匿名表单入口不再提供该表单。
- **需要你做的**:无,席位落地。
## What changes
- **`carryFormWithdrawals`**
(`packages/cli/src/utils/organization-ownership-apply.ts`). For a served
view whose package is read-only, the carry now goes through
`carryIntoPair`: it ensures the base's `.custom` pair, then saves the
withdrawn view into the pair through the metadata door, declaring
`overrides: { package: BASE, basedOn }`. A body that already declares
the base (the pair's own override, as served) keeps its declaration. The
withdrawal counts in `summary.withdrawalsCarried`, and the progress line
names the pair and the version.
- **A writable package's view** is saved in place, as before; no pair is
ensured. A package-less view is untouched by the new branch. The
`single` skip for a view whose key an organization row holds is
unchanged.
- **When the pair cannot take it** (the pair cannot be ensured, the
writability read throws, or the door refuses the declared save), the
carry makes the save naming the package as before. Where the door admits
that save, it carries the withdrawal as before. Where it refuses, the
step blocks with the same sentence as before, plus one clause naming the
pair's refusal (`Nor could its customization package take it: ...`).
- **New port** `CustomizationPairPort` (`readOnly`, `ensure`), optional
on `ApplyOptions` as `pairs`. Absent, the carry behaves exactly as on
main. `protocolCustomizationPairs(...)` builds it from the booted stack.
- **The command wiring**
(`packages/cli/src/commands/migrate/organization-ownership.ts`):
`readOnly` is `isWritablePackage(engine, id)` from
`@objectstack/metadata-protocol`, negated. That is the predicate the
save door judges by, not a second reading. `ensure` is the protocol's
in-process `ensureCustomizationPackage` (the body of `POST
/packages/:id/ensure-customization`). `basedOn` is the installed base's
`manifest.version`, the value the named-base close and `os migrate
packageless-overlays` read too.
- **Changeset** `.changeset/29396-withdrawal-carry-into-pair.md`
(`@objectstack/cli` patch). It says the manual step in the pending
named-base changeset is no longer needed when the pair can be ensured.
That changeset is not edited.
## Tests
- **New integration file**
`packages/cli/src/commands/migrate/organization-ownership.withdrawal-pair.integration.test.ts`.
It runs the real command in process through oclif, plan then `--apply`,
over a database a served boot of this tree provisioned. The project's
config is a booted code package (read-only) shipping a public form view.
An organization's row of that view withdraws the form, and
`sys_metadata` carries `organization_id` as on v17. The test pins four
things. The step carries 1. The pair's row declares `{ package: BASE,
basedOn: '1.4.0' }` with `enabled: false`. No row is bound to the base.
A fresh boot (with the package service, so the pair installs from
`sys_packages`) serves the base's form withdrawn, and the env-wide list
the anonymous doors judge by serves the slug from no view. A second
`--apply` finds the step checkpointed and the stored rows unchanged.
- **Five new cases** in
`packages/cli/src/utils/organization-ownership-apply.test.ts`, in a
nested describe:
- ⭐ The read-only view is carried into the pair, declared at the base's
version. The doors serve it withdrawn, and a re-run with no checkpoint
carries nothing.
- A writable package's view is saved in place, and no pair is ensured.
- A pair that cannot be ensured keeps the blocker, which names both
refusals, and nothing is written.
- A refused declared save into the pair keeps the blocker, naming that
refusal.
- Control: where the pair cannot take it and the door admits the save
naming the package, that save carries it.
- Forced test edits: none. The test file's `apply(...)` helper gained
two pass-through options (`pairs`, `onProgress`), and no existing
assertion changed.
## Why
**Step 0, measured on the real command** (the integration file above).
The main column is `aca58cdb` with this branch's test file only. The
head column is this branch.
| Reading | main `aca58cdb` | head |
|---|---|---|
| Precondition: the shipped form is served open | yes (`wdp_lead.intake`
of `com.example.wdpair`) | yes |
| `metadata-promotion` | `blocked`, `withdrawalsCarried: 0` | `done`,
`withdrawalsCarried: 1` |
| Ceremony | `complete: false`; the 10 later steps `not-reached` |
`complete: true`, `blocking: 0` |
| Stored rows of the view | the promoted organization row (no package)
only | that row, plus `com.example.wdpair.custom` declaring `overrides:
{ package: 'com.example.wdpair', basedOn: '1.4.0' }`, `enabled: false` |
| Row bound to the read-only base | none | none |
| Fresh boot: views serving the slug open | not measured (the case stops
at the blocker) | none |
The blocker on main, verbatim:
> An organization withdrew the public form(s) wdp-intake of the view
wdp_lead.intake (package com.example.wdpair), and the metadata door
refused to carry that withdrawal onto the environment:
'view/wdp_lead.intake' ships in 'com.example.wdpair': save it into
'com.example.wdpair.custom' (?package=com.example.wdpair.custom),
declaring overrides: { package: 'com.example.wdpair', basedOn }. The
read-only base itself takes no customization (ADR-0146 D1, D11): a row
bound to it from before is served until the pair declares the override,
or a delete naming the base removes it.. Withdraw the form there
(sharing.enabled false), then re-run. An upgrade never reopens a
withdrawn form (ruling record 6020151485).
**How the cli reaches the pair** (read on main):
| Question | Answer |
|---|---|
| Can the ceremony's door (`run.opts.door`) ensure the pair? | No. It is
`protocol.saveMetaItem` alone. The protocol service the command already
holds exposes `ensureCustomizationPackage` in process, the body of `POST
/packages/:id/ensure-customization`, so the command wires it as a second
port. |
| How is the override declared? | In the saved body: `overrides: {
package: BASE, basedOn: VERSION }`, saved naming `BASE.custom` (the
dogfood helpers `ensurePair` / `declaredOverride` have the same shape).
|
| Where does `basedOn` come from? | The installed base's
`manifest.version` from the engine registry: the value the named-base
close checks and `os migrate packageless-overlays` records. |
| Which packages are read-only? | `isWritablePackage(engine, id)`, the
save door's own predicate (ADR-0070 D2): a booted code package or a
`system` / `cloud` scope. |
## Acceptance notes
- When the pair cannot take the withdrawal, the carry falls back to the
save naming the package. That is main's behaviour, kept on purpose. The
door's named-base close refuses only where the pair can take the write,
so where the pair cannot (no namespace, no version, an existing pair
lacking the dependency), the door still admits the save, as on main. The
card's "keep today's blocker" holds wherever the door refuses.
- Observed, unchanged: under `single`, the organization's package-less
row of the view is promoted in place (`promoted: 1`), so the environment
keeps a package-less row of a shipped name beside the pair's override.
Both have the form withdrawn. Moving such rows into the pair is `os
migrate packageless-overlays`' job, not this step's.
## Verification
All readings are on head `986b2d01` (this branch with `origin/main`
merged) unless named. Heavy runs went through
`scripts/pm/os-verify-lock.sh`.
<details><summary>Tests: 123/123 in the touched files, cli unit tier
374/374, ceremony dogfood files 34/34</summary>
- `pnpm --filter @objectstack/cli exec vitest run --project integration
src/commands/migrate/organization-ownership.withdrawal-pair.integration.test.ts
src/utils/organization-ownership-apply.test.ts
src/utils/organization-ownership-apply.served-catalog.integration.test.ts`:
Test Files 3 passed (3), Tests 123 passed (123), VERDICT command-exit 0.
- `pnpm --filter @objectstack/cli exec vitest run --project unit`: Test
Files 372 passed, 2 failed (374). Both failures were prerequisites, not
results: `test/published-subpath-console.pin.test.ts` and
`test/published-subpath-hook-body.pin.test.ts` said "packages/cli is not
built (./dist/index.js is absent)". After the cli was built, those two:
Test Files 2 passed (2), Tests 29 passed (29).
- The dogfood files that run this ceremony or its form withdrawal, after
`turbo run build --filter='@objectstack/dogfood^...'`:
`upgrade-18x-harness`, `organization-ownership-boot-refusal-order`,
`upgrade-18x-index-equivalence` and
`showcase-public-form-withdrawal-layers`. Test Files 4 passed (4), Tests
34 passed (34), VERDICT command-exit 0. The full battery was not run:
the title has no `!` and there is no narrowing arm.
- `pnpm --filter @objectstack/cli typecheck`: exit 0 (tsc, plus
check:test-typecheck OK with debt held).
- `pnpm lint` scoped to the package (`eslint packages/cli
--no-inline-config`): exit 0.
</details>
<details><summary>Ablations: the pair branch, and the writability
read</summary>
Each was planted with `scripts/ablation-replace.mjs` (anchor hit 1 time,
blob `b629ad6b6c29` changed, restored to `b629ad6b6c29`), then `git diff
HEAD` was empty (0 bytes) and `git status` clean. The cli tests import
`src` directly, so no dist leg is needed.
1. The pair branch switched off (`if (packageId !== null &&
run.opts.pairs && false)`), which puts back main's save naming the base.
4 failed / 114 passed. The unit cases: the read-only carry (`expected []
to deeply equal [ 'pkg_a' ]`, no pair ensured), the unensurable-pair
blocker and the refused-pair blocker (each blocker lost its pair
clause). The real-command integration case failed too.
2. The writability read ignored (every package treated as read-only).
The writable case went red: the save went into `pkg_a.custom` with a
declaration instead of `pkg_a` in place.
</details>
<details><summary>Derived gates: 62 run, 0 NOT-MEASURED</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `986b2d01` derived 62 commands. All were
run, and `--ran` reconciled: 62 derived, 62 run, 0 NOT-MEASURED, 0
UNRUN.
Five answered exit 3 (prerequisite not met) on the first pass and exit 0
when re-run once the prerequisite was in place:
- `check-plugin-teardown-shape --self-test`: its pinned fixture commit
was fetched.
- `check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-walk-parity`:
the cli dist was built (the dogfood closure).
- `check:i18n-coverage`: packages and examples were built.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No `packages/spec` change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (eslint on `packages/cli`,
exit 0).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
maintainer's Chinese brief).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQTt48dSV5oqZ5jUhK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent aee9c47 commit 1e9f72d
5 files changed
Lines changed: 517 additions & 4 deletions
File tree
- .changeset
- packages/cli/src
- commands/migrate
- utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
751 | 752 | | |
752 | 753 | | |
753 | 754 | | |
754 | | - | |
| 755 | + | |
755 | 756 | | |
756 | 757 | | |
757 | 758 | | |
| |||
774 | 775 | | |
775 | 776 | | |
776 | 777 | | |
| 778 | + | |
777 | 779 | | |
778 | 780 | | |
779 | 781 | | |
| |||
926 | 928 | | |
927 | 929 | | |
928 | 930 | | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
929 | 946 | | |
930 | 947 | | |
931 | 948 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
0 commit comments