Repository navigation
Commit 3a0aa91
fix(cloud-connection)!: install-local's cloud branch refuses a package declaring a bare name another package holds (#31388)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XTVkGrf2CjQTF79hBERgqiJ)_
domain:server#5 · os-project-manager · session_01FEkQ5ktJb5rQ5n2GUXSQMR
Fixes #31296
Clause-②: no (narrowing) — a cloud-branch install that answered success
(`hotLoaded: false`) is now refused 422; one contract-review-tier review
owed before the queue
Metadata-protocol: none
## 维护者速读
- 改了什么:`POST /api/v1/marketplace/install-local`
的云端分支(从控制面下载的包)在注册被「同名只能一个包持有」规则(#30803 系列:flow、webhook、dataset
等)拒绝时,现在和内联分支一样直接回 `422
PLUGIN_REGISTER_FAILED`,消息点名两个持有者和建议的改名,不写安装记录、不注册。
- 为什么改:之前云端分支只认「环境已持有的安全目录名」这一种拒绝,其余都走宽容路径:写安装记录、回 200 和 `hotLoaded:
false`。下次重启时这个包在封印之前就被恢复,冷启动只告警一次就接受了冲突,同名的使用方按注册顺序拿到其中一个包的定义。
- 风险与回滚:只收窄一个入口的一种拒绝;其它注册失败照旧走宽容路径,环境持有的目录名照旧 422。识别靠错误自身的 code +
name,不列类型,#30803 后续新增的类型自动覆盖。回滚即还原本 PR。
- 先看:`packages/cloud-connection/src/marketplace-install-local-plugin.ts`
新函数 `refusesBareNameHeldElsewhere` 和 catch 分支里那一行 `if (...)`;然后
`marketplace-install-local-bare-name-conflict.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/cloud-connection exec vitest run
src/marketplace-install-local-bare-name-conflict.test.ts`,预期 4 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
`packages/cloud-connection/src/marketplace-install-local-plugin.ts`, the
cloud-fetched branch of `POST /api/v1/marketplace/install-local` (a
`packageId` + `versionId` body):
1. **One new predicate, `refusesBareNameHeldElsewhere(err)`.** It
recognises the registry's bare-name refusal (`BareNameConflictError`,
`registry.ts` in `@objectstack/objectql`) by the error's own brand: the
code it shares with its siblings (`NAMESPACE_CONFLICT_CODE`, already
imported here) plus its `name`. This is the code + name pair
`isGuestOrganizationUnresolvedError` (`@objectstack/core`) and the
`DuplicateRecordError` readers (types, metadata-protocol) use for an
unexported class. It never reads the error's types or holders: which
types the rule covers and who holds a name stay the registry's.
2. **The register catch asks it beside #25366's predicate.** When either
answers yes, the cloud branch answers `422 PLUGIN_REGISTER_FAILED` with
the registry's sentence (both holders, the name, the rename), before the
ledger write and before anything else of this door's is written. The
registry's package door already refused before its own writes.
3. **Doc comments.** The module header's install paragraph, the import
comment and the catch's comment say why the lenient answer is wrong
here: the ledger entry is rehydrated at the next boot before the cold
boot seals the holders, so the boot admits the clash with one warning
(`BARE_NAME_COLD_BOOT_MODE = 'warn'`).
Not changed: any other register failure on the cloud branch keeps
#22695's lenient answer (`200`, `hotLoaded: false`, `hotLoadError`); the
inline branch; the environment-held security catalog refusal.
**Parallel work.** The `#30803 PR 4` thread (agent + skill) adds two
more types to the same one-holder list. This predicate does not
enumerate types, so those types are refused here without a change once
they land.
## Tests
New
`packages/cloud-connection/src/marketplace-install-local-bare-name-conflict.test.ts`,
over a real `ObjectKernel` + `ObjectQLPlugin` booted to
`kernel:bootstrapped` (holders sealed), the holding package installed
through the same door, the cloud leg a stubbed `fetch`:
- a fresh cloud-branch install of a package declaring a flow name
another package holds: 422 `PLUGIN_REGISTER_FAILED` in the declared
envelope (`BaseResponseSchema`, `ApiErrorSchema`, `envelopeViolations`
empty), the message naming both packages, the flow and the rename
`routing_assign_technician`; the package not registered; the ledger
equal to before; the holder's flow still the holder's;
- the same for an upgrade over an installed version 1: version 1 still
registered and ledgered;
- CONTROL: an environment-held security catalog name keeps its 422
naming the environment catalog (the full matrix stays in
`marketplace-install-local-catalog-name-collision.test.ts`);
- CONTROL: the same package declaring a flow name nobody holds installs,
`200`, `hotLoaded: true`, registered and ledgered.
The lenient answer for a non-registry register failure stays pinned by
`marketplace-install-local-hot-loaded.test.ts`.
<details><summary>Red on main (the new test run against the unfixed
plugin at base d0512c2, before the fix was written)</summary>
```
× a fresh install is refused: 422 naming both holders and the rename, nothing registered or ledgered
× an upgrade is refused: 422, the installed version still registered and ledgered
AssertionError: {"success":true,"data":{"manifestId":"com.acme.routing","version":"1.0.0","versionId":"v1-held",…,"hotLoaded":false,"hotLoadError":"Bare-name conflict: package \"com.acme.routing\" cannot register a name another holder already holds — package \"com.acme.routing\" declares the flow \"assign_technician\", already held by package \"com.acme.dispatch\" — rename it to \"routing_assign_technician\" in \"com.acme.routing\". …"}}: expected 200 to be 422
Tests 2 failed | 2 passed (4)
```
</details>
<details><summary>Ablation (fix committed, predicate removed from the
catch, restored)</summary>
`node scripts/ablation-replace.mjs --file
packages/cloud-connection/src/marketplace-install-local-plugin.ts
--anchor "if (refusesEnvironmentHeldCatalogName(err) ||
refusesBareNameHeldElsewhere(err)) {" --replacement "if
(refusesEnvironmentHeldCatalogName(err)) {" -- …vitest run
src/marketplace-install-local-bare-name-conflict.test.ts`
```
ablation-replace: ok mutation landed: anchor 1 -> 0, blob 09ac8e8 -> 2268f74afe54
× a fresh install is refused: 422 naming both holders and the rename, nothing registered or ledgered
× an upgrade is refused: 422, the installed version still registered and ledgered
Tests 2 failed | 2 passed (4)
ablation-replace: ok restored: blob == HEAD (09ac8e8) and `git diff HEAD` is empty
```
The suite imports the plugin from `src/` relatively, so the mutation
needs no build.
</details>
## Verification
All on head `7b59e006` (the fix, the changeset, and one merge of
`origin/main` 8b90442), exit codes captured before any pipe:
- **Derived gate union**, `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`: 69 families, every one exit 0.
`--ran`: "69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED
(a DERIVED zero — all 69 recorded an exit code and none of them is 3)".
Four first answered a prerequisite and were rerun once it held:
`check-plugin-teardown-shape --self-test` (3, shallow clone; 0 after
`git fetch --depth=1 origin 621a487`), `check:dts-closure` (1) and
`check:dual-build-cjs-loads` (3), both 0 after `pnpm turbo run build
--concurrency=2` (VERDICT command-exit 0), and `check:type-check-debt`
(3, lock queue behind the dogfood run; 0 on rerun).
- **Package**: `pnpm --filter @objectstack/cloud-connection test`: Test
Files 74 passed, Tests 973 passed (VERDICT 0). `pnpm --filter
@objectstack/cloud-connection typecheck` (src and `tsconfig.test.json`,
which lists the new test file): exit 0.
- **Lint**: `eslint --no-inline-config` on the two touched `.ts` files:
exit 0.
- **Full dogfood battery** (`(narrowing)` arm), `pnpm --filter
@objectstack/dogfood test` after the `@objectstack/dogfood^...` closure
build, under the verify lock: VERDICT command-exit 0, Test Files 437
passed / 3 skipped (440), Tests 3618 passed / 41 skipped (3659).
- CI-only families (Test Core shards, the type-check lanes, Build Docs)
are read from this PR's check-runs, not re-run here.
## Acceptance notes
- **Security catalog, package or built-in holder (not fixed here).** The
same lenient path still takes the security catalog's refusal when the
holder is another package or a built-in. Measured with this PR's harness
on a throwaway probe: a cloud-branch install of a package shipping a
permission set another installed package ships answers `200`,
`hotLoaded: false`, not registered, ledgered. Read from code: the
package door's security catalog check has no seal, so the rehydrate at
the next boot is refused the same way and the package never loads.
Outside this card's claimed predicate; reported to the seat for filing.
- **Old ledger entries.** An entry an earlier build ledgered this way
keeps loading at boot beside the cold boot's warning; this PR refuses
new ones only.
- **Governed surface.** None: the diff is one source file, one test file
and one changeset.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5ktJb5rQ5n2GUXSQMR
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5ktJb5rQ5n2GUXSQMR)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4a3b186 commit 3a0aa91
3 files changed
Lines changed: 356 additions & 5 deletions
File tree
- .changeset
- packages/cloud-connection/src
| 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 | + | |
| 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 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
0 commit comments