Repository navigation
fix(cli): os migrate organization-ownership --apply carries a public-form withdrawal onto a read-only package view into its .custom pair, declared - #29508
Conversation
…to its .custom pair (wip) Claude-Session: https://claude.ai/code/session_01ADHkmQTt48dSV5oqZ5jUhK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQTt48dSV5oqZ5jUhK Co-authored-by: Claude <noreply@anthropic.com>
…ointed and writes nothing Claude-Session: https://claude.ai/code/session_01ADHkmQTt48dSV5oqZ5jUhK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQTt48dSV5oqZ5jUhK Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dddf84560fe2234c3d15b73f604a68ccf42ecc87 && git checkout dddf84560fe2234c3d15b73f604a68ccf42ecc87
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 91f6a0b4e58e02e25e2921a33f8f8690ec1b2937 986b2d01c7f40a5fdcda0d3d6953c8e338d718c4 && git checkout -B drift-repro 91f6a0b4e58e02e25e2921a33f8f8690ec1b2937 && git merge --no-ff 986b2d01c7f40a5fdcda0d3d6953c8e338d718c4
node scripts/docs-audit/affected-docs.mjs --json 91f6a0b4e58e02e25e2921a33f8f8690ec1b2937
|
|
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY Contract-review-claim: session_01ADHkmQjPiXgNoSaEpqeGvY · head 986b2d0 · 2026-11-02T16:55Z |
|
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY Contract reviewServed-tier: Inputs read: card #29396 (body, the triage, both claims, the dev's report 6582986079); the source record 6577747588 on PR #29270 (S4, open question 2); PR #29508's body, its file list (5 files) and the net diff against its merge base ① Derived judgments
② Semver level
③ Boundary flags
Must-fix: none. Should-fix: none. Nit: ① 9 (an ensured pair can survive a fallback), no action. Implemented-by: VERDICT: PASS |
|
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY 席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | Draft, base main (origin/main merged in), Fixes #29396. Five files, all under packages/cli: organization-ownership-apply.ts (carryFormWithdrawals → carryIntoPair), the command wiring commands/migrate/organization-ownership.ts, one unit and one integration test, and a changeset (@objectstack/cli: patch, Clause-②: no, Metadata-protocol: none). The door, the close predicates and packages/spec/** are untouched. Not governed (check-governed-merges governed: false). |
| Rule | A withdrawal on a read-only package's view is carried into BASE.custom. The pair is ensured in process (ensureCustomizationPackage), and the save goes through saveMetaItem with overrides { package, basedOn: manifest.version }. A writable package's view is saved in place as before. Where the pair cannot take the write, the carry falls back to main's save, and the door judges it again. A re-run is a no-op. |
| Evidence | Base, through the real command: blocked on NOT_OVERRIDABLE, withdrawalsCarried 0, nothing written. Head: carried, and a fresh boot serves the form withdrawn. 2 ablations went red and were restored. cli 374/374, touched files 123/123, ceremony dogfood 34/34, gates 62/62. CI on this head: 48 runs, 35 green, 13 skipped, none red. check-expected-skips OK (13, all in main's roster). |
| Open question | OQ1: A, keep main's save as the fallback. The door admits it exactly where the pair cannot take it. B would add a refusal and falsify Clause-②: no. |
| Nit, not filed | If the pair is ensured and the save into it is then refused, an empty pair can stay installed. It is harmless: the pair is the place ADR-0146 names, and the next administrator customization reuses it. |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay. |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41489549492 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY Queue triage · 2026-11-02T17:37Z — the red is not this PR's.
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41491863627 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Steve · project thread
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: 基础包当前版本 }。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 throughcarryIntoPair: it ensures the base's.custompair, then saves the withdrawn view into the pair through the metadata door, declaringoverrides: { package: BASE, basedOn }. A body that already declares the base (the pair's own override, as served) keeps its declaration. The withdrawal counts insummary.withdrawalsCarried, and the progress line names the pair and the version.singleskip for a view whose key an organization row holds is unchanged.Nor could its customization package take it: ...).CustomizationPairPort(readOnly,ensure), optional onApplyOptionsaspairs. Absent, the carry behaves exactly as on main.protocolCustomizationPairs(...)builds it from the booted stack.packages/cli/src/commands/migrate/organization-ownership.ts):readOnlyisisWritablePackage(engine, id)from@objectstack/metadata-protocol, negated. That is the predicate the save door judges by, not a second reading.ensureis the protocol's in-processensureCustomizationPackage(the body ofPOST /packages/:id/ensure-customization).basedOnis the installed base'smanifest.version, the value the named-base close andos migrate packageless-overlaysread too..changeset/29396-withdrawal-carry-into-pair.md(@objectstack/clipatch). 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
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, andsys_metadatacarriesorganization_idas on v17. The test pins four things. The step carries 1. The pair's row declares{ package: BASE, basedOn: '1.4.0' }withenabled: false. No row is bound to the base. A fresh boot (with the package service, so the pair installs fromsys_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--applyfinds the step checkpointed and the stored rows unchanged.packages/cli/src/utils/organization-ownership-apply.test.ts, in a nested describe: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
aca58cdbwith this branch's test file only. The head column is this branch.aca58cdbwdp_lead.intakeofcom.example.wdpair)metadata-promotionblocked,withdrawalsCarried: 0done,withdrawalsCarried: 1complete: false; the 10 later stepsnot-reachedcomplete: true,blocking: 0com.example.wdpair.customdeclaringoverrides: { package: 'com.example.wdpair', basedOn: '1.4.0' },enabled: falseThe blocker on main, verbatim:
How the cli reaches the pair (read on main):
run.opts.door) ensure the pair?protocol.saveMetaItemalone. The protocol service the command already holds exposesensureCustomizationPackagein process, the body ofPOST /packages/:id/ensure-customization, so the command wires it as a second port.overrides: { package: BASE, basedOn: VERSION }, saved namingBASE.custom(the dogfood helpersensurePair/declaredOverridehave the same shape).basedOncome from?manifest.versionfrom the engine registry: the value the named-base close checks andos migrate packageless-overlaysrecords.isWritablePackage(engine, id), the save door's own predicate (ADR-0070 D2): a booted code package or asystem/cloudscope.Acceptance notes
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 isos migrate packageless-overlays' job, not this step's.Verification
All readings are on head
986b2d01(this branch withorigin/mainmerged) unless named. Heavy runs went throughscripts/pm/os-verify-lock.sh.Tests: 123/123 in the touched files, cli unit tier 374/374, ceremony dogfood files 34/34
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.tsandtest/published-subpath-hook-body.pin.test.tssaid "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).turbo run build --filter='@objectstack/dogfood^...':upgrade-18x-harness,organization-ownership-boot-refusal-order,upgrade-18x-index-equivalenceandshowcase-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 lintscoped to the package (eslint packages/cli --no-inline-config): exit 0.Ablations: the pair branch, and the writability read
Each was planted with
scripts/ablation-replace.mjs(anchor hit 1 time, blobb629ad6b6c29changed, restored tob629ad6b6c29), thengit diff HEADwas empty (0 bytes) andgit statusclean. The cli tests importsrcdirectly, so no dist leg is needed.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.pkg_a.customwith a declaration instead ofpkg_ain place.Derived gates: 62 run, 0 NOT-MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat986b2d01derived 62 commands. All were run, and--ranreconciled: 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.Human Minimum
packages/specholds no runtime logic. (Nopackages/specchange.)pnpm lintpasses (eslint onpackages/cli, exit 0).main, one pull request.packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code