Repository navigation
fix(cloud-connection,cli): install a .custom export into the target's writable pair - #30207
Conversation
… pair Claude-Session: https://claude.ai/code/session_01Utp6CXfNPQKEBNuyohXjfg Co-authored-by: Claude <noreply@anthropic.com>
… writable pair An exported `<base>.custom` installed through install-local was registered as code: none of its overrides was served, the customization read listed none, and the pair turned read-only, while the install answered success. A pair is now installed as what it is (ADR-0146 D2 / D3): the target's own pair through its ensure door, every item written through the save door with `mode: 'publish'`, nothing registered, synced or ledgered. Refusals the body or the target decide are answered before any write; a save refused part-way answers failure, removing a pair the install created. A pair already read-only on the target is refused 409 naming the way back. The CLI prints the pair it wrote, and prints an older runtime's as-code answer as an error. Claude-Session: https://claude.ai/code/session_01Utp6CXfNPQKEBNuyohXjfg Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CXfNPQKEBNuyohXjfg Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CXfNPQKEBNuyohXjfg Co-authored-by: Claude <noreply@anthropic.com>
…, in its catch The install the operator asked for did not land, and the pair is not what the export describes; the durability gate reads the catch, so the line is written there with the save's own words. Claude-Session: https://claude.ai/code/session_01Utp6CXfNPQKEBNuyohXjfg Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 36 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 38 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 1d621cbbda45dd06bc5c79f61e2992bb6a102259 && git checkout 1d621cbbda45dd06bc5c79f61e2992bb6a102259
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dfafae81324e508cc666171d3385a15b89f31050 6be0aea6a64d61bac1d2831d33da398c967deee7 && git checkout -B drift-repro dfafae81324e508cc666171d3385a15b89f31050 && git merge --no-ff 6be0aea6a64d61bac1d2831d33da398c967deee7
node scripts/docs-audit/affected-docs.mjs --json dfafae81324e508cc666171d3385a15b89f31050
|
|
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC Contract reviewServed-tier: Face, read off the committed tree (10 files, ① Derived judgmentsAccept-set and public-surface changes the diff makes, each judged:
② Semver level
③ Boundary flags
④ Design judgmentAgainst ADR-0146 D2/D3 and the card's acceptance:
No must-fix found. Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC 席位核对ACCEPT · head
|
Requested by Bill · project thread
Fixes #30106
Clause-②: no
Metadata-protocol: none
维护者速读
.custom定制包,装到另一个环境时,现在写进目标环境自己的可写配对包:先走配对包的 ensure 门,再逐项走 Studio 编辑用的保存门。不再当成代码注册。.custom结尾的配对包安装。某项保存被拒时,本次新建的配对包整包删掉;原有的配对包保留,答复列出写了和没写的项。回滚就是 revert 本 PR。packages/cloud-connection/src/marketplace-install-local-plugin.ts(installCustomizationPair与pairInstallWriteFailure);packages/cloud-connection/src/marketplace-install-local-customization-pair.ts(哪些键算条目、写入顺序);packages/cli/src/commands/package/install.ts(输出)。pnpm --filter @objectstack/cloud-connection exec vitest run src/marketplace-install-local-customization-pair-install.test.ts,预期 15 条全绿。What changes
POST /api/v1/marketplace/install-localnow installs a base's paired customization package (BASE.custom, whatGET /packages/BASE.custom/exportwrites) as what ADR-0146 D2 / D3 say it is: an ordinary writable package whose items are stored rows.installCustomizationPair,marketplace-install-local-plugin.ts). The steps run in this order, and nothing is written before the last one:planCustomizationPairInstall, new pure modulemarketplace-install-local-customization-pair.ts).customizationPairManifestRefusal). A pair already read-only here is refused 409 before the ensure door is asked.saveMetaItem, in registry load order, withmode: 'publish', the pair as its package, and the installer as actor.Nothing is registered as code, schema-synced, or written to the cached-manifest directory. The answer carries
customizationPair(basePackageId,packageId,created,writable),writtenCountandwritten.PLURAL_TO_SINGULAR),viewItemsandobjectExtensions(asobject_extensionitems named by their target), plus the package's own keys. A key with no item form is refused 422 and every problem is listed. That covers code, atranslationsbundle, an envelope, and a key the install-key table does not classify. A nameless item, and two items with one identity, are refused the same way. Anemail_templateis keyed by(name, locale), as the save door keys its row. Seeddatais reported as not loaded.errorwith the save's own words. A pair this install created is deleted again; if that deletion does not finish, the plugin logs aterror. A pair that existed before is kept, and the answer lists what was and was not written.details.pairs): a pair is installed on its own, after its base.os package install) prints the pair it wrote into and the item count. A runtime that predates this answers a pair as a managed install. The CLI now prints that answer as an error with the way back and exits 1, instead of printing success.Tests
marketplace-install-local-customization-pair.test.ts, the planner (7 cases).marketplace-install-local-customization-pair-install.test.ts, the door at its route (15 cases):created: false;packages/cli/test/package-install-customization-pair.test.ts(2 cases):marketplace-install-local-override-gate.test.ts(3 cases) andmarketplace-install-local-extension-field-suffix.test.ts(1 case) pinned a pair registered as code (registered === [PAIR]). They now pin it written into its pair (registered === [], saves named). Their harness gains the pair's doors, and the base declares the namespace its pair's is derived from.packages/cli/test/package-export.test.ts(1 case). Its round-trip stub now answers the pair entry in the pair shape.@objectstack/cloud-connectionfull suite at6be0aea6a: Test Files 70 passed (70), Tests 846 passed (846).@objectstack/cliunit project (vitest run --project unit): 382 of 383 files passed, and 5381 of 5382 tests. The one failure wastest/package-export.test.ts, whose round-trip stub answered the exported pair as code. It now answers the pair shape, and the file passes 6/6. Theintegrationtier is left to CI.package-export-reinstall-custom-pairandpackage-export-bundle-round-trip(2 files, 16 tests) pass at6be0aea6a. The second installs a pair entry on a fresh target and re-exports it.Acceptance notes
Clause-②: no. On measurement, two facts may read as an arm, and they are left to the seat:customizationPair,writtenCountandwritten, and dropshotLoadedandstorageDir.LocalManifestSource's default,CWD/.objectstack/installed-packages, and is not keyed byOS_HOME. That breaksos dev --freshisolation: a ledgered entry rehydrates under a newOS_HOME. A pair no longer writes one at all. Moving the directory for other packages needs a read-old-location fallback, so this PR leaves it. Measured: the pre-fix install wroteexamples/app-showcase/.objectstack/installed-packages/com.example.showcase.custom.jsonunder a freshOS_HOME, and a fresh boot rehydrated an entry placed there.DELETE /api/v1/marketplace/install-local/ID, the package still reads read-only until a restart. The engine's manifest map keeps the id.POST /packages/BASE/ensure-customizationthen answered201 created: true, writable: false. Measured on the pre-fix and fixed builds. The 409 above names the restart for this reason.emailTemplatesand translation items, so those overrides do not round-trip;--install-mode unmanagedon a pair still takes the unmanaged copy path;overrideWriteItemsOfdoes not readviewItems(for a pair the save door judges each item).packages/specedit. The file surface is the claim's:packages/cli/src/commands/package/**, plus the install door thatpackages/runtime/src/domains/packages.tshands install-local to (packages/cloud-connection/src/marketplace-install-local-*.ts).Verification
Dev measurement, two
os devshowcase runtimes. Source: customize a view and a dashboard into the pair, publish,os package export. Target:os package installthe pair'sobjectstack.json.Before the fix (main 07595c0)
After the fix: fresh target
After the fix: a target holding the pair as code from an earlier install
Gates at 6be0aea
🤖 Generated with Claude Code
Generated by Claude Code