Repository navigation
Commit a06feb7
fix(cloud-connection)!: install-local judges a ledger entry in the CLI's lifted shape through the install door's flatten, at boot and on the sample-data doors (#28683)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XQQCpJU17CHdHiXkbKYBsc8)_
Fixes #28604
Clause-②: no (narrowing) — at `kernel:ready` the install-local rehydrate
no longer loads, and `POST
/api/v1/marketplace/install-local/:manifestId/reseed-sample-data` and
`/purge-sample-data` answer 422 `OS_PROTOCOL_INCOMPATIBLE` for, a ledger
entry an install wrote before #28535 in the body `os package install
FILE` sends (`id` and `version` lifted beside a nested `manifest`) whose
nested `engines.protocol` excludes this runtime. That entry loaded, and
those doors acted on it, before. A flat entry with the same range was
already refused the same way. Nothing is widened.
Metadata-protocol: none
Skills-docs: none — the pages already state the behaviour this restores:
`skills/objectstack-platform/SKILL.md` ("The runtime checks it before it
loads anything and refuses an out-of-range app at the boundary"). No
page described a ledgered CLI install as an exception.
**BREAKING** boot-load and accept-set narrowing for already-ledgered
install-local entries (`@objectstack/cloud-connection`): the
`kernel:ready` rehydrate and the two sample-data doors, reached by a
deployment where `os package install ./dist/objectstack.json` ran before
PR #28603.
## 维护者速读
- **改了什么**:启动时重新加载已安装应用(`kernel:ready`
rehydrate)、以及「重置/清除示例数据」两个接口,读台账条目时先经安装接口已有的同一个展平函数(`flattenPackageBody`)再做协议握手;rehydrate
的依赖排序也读展平后的条目。
- **为什么改**:卡 #28604。PR #28603 之前,`os package install`
发来的产物(`id`/`version` 提到顶层、`engines` 嵌在 `manifest` 下)原样写进台账。启动时只看顶层
`engines`,读不到嵌套的范围,于是不兼容协议的包每次启动都照常加载,没有任何报错。
- **风险与回滚**:只影响「PR #28603 之前用 CLI 装的、且协议范围不兼容」的台账条目:现在与平铺条目同样处理(不加载、一条
error 日志、列表标 `notLoaded`、两个示例数据接口答 422,DELETE
与兼容版本重装照常)。兼容的条目照旧加载,注册的内容与台账一字不差。回滚即 revert。
-
**先看**:`packages/cloud-connection/src/marketplace-install-local-plugin.ts`(rehydrate
握手
≈:1109、`inDependencyOrder`、`refuseProtocolIncompatibleEntry`、`flattenPackageBody`
的空值守卫);`marketplace-install-local-protocol-handshake.test.ts` 新
`[#28604]`
用例组;`.changeset/28604-install-local-rehydrate-lifted-entry.md`。
- **两分钟验证**:`pnpm --filter @objectstack/cloud-connection exec vitest run
src/marketplace-install-local-protocol-handshake.test.ts
src/marketplace-install-local-sample-data-not-loaded.test.ts
src/marketplace-install-local-rehydrate-order.test.ts` → 37/37 通过。
- **需要你做的**:无,席位落地(收窄需先过合约评审)。
## What changes
**The one flatten, read at three more places**
(`packages/cloud-connection/src/marketplace-install-local-plugin.ts`).
An install before PR #28603 ledgered the CLI's body as it arrived: `{
id, version, manifest: { …, engines, dependencies }, objects, data, …
}`. Every reader that decides by a manifest key read it at the top, so
the nested keys were invisible. Each now reads the entry through
`flattenPackageBody`, the function the install door already uses (no
second flatten, no second handshake):
- the `kernel:ready` rehydrate's
`checkProtocolCompat(flattenPackageBody(entry.manifest))`, so an
excluded nested range is not loaded, is recorded in `refusedAtRehydrate`
(the listing's `notLoaded` marker) and gets the one error line, exactly
as a flat entry;
- `refuseProtocolIncompatibleEntry`, the reseed/purge doors' handshake,
so both answer the install route's 422 for it before any side effect;
- `inDependencyOrder`, so a lifted entry is ordered by the
`dependencies` its nested manifest declares (ADR-0141 D5). At boot, a
flat entry's dependencies are used for this ordering only (the rehydrate
runs no dependency check: `checkDeclaredDependencies` is called only at
the install door), so ordering is the parity a lifted entry is owed.
`flattenPackageBody` gains a `!isPlainObject(body)` guard so it is total
over a ledger value, which `LocalManifestSource` does not shape-check.
Measured: a `null`, a string and a number `manifest` produce
byte-identical rehydrate logs, registrations and door answers on base
and head (probe in the evidence folder below).
**What stays as today.** A compatible lifted entry registers, converts,
binds and seeds the body exactly as ledgered: only the judging and the
ordering read the flattened view. The install door and its #28603
flatten are untouched.
## Why
ADR-0087 D1 puts the handshake "before loading a package's metadata",
and a rehydrate is a load (#21762). The triage (`6557784727`) graded the
reach p3: one closed population, the entries a CLI install wrote before
PR #28603 (`7449e479`), since new installs are refused at the door.
## Tests
- `marketplace-install-local-protocol-handshake.test.ts`, new group
`[#28604] kernel:ready rehydrate of an entry ledgered in the CLI's
lifted shape`: the lifted `^17` entry is not registered, nothing is
synced, and its error lines equal a flat `^17` entry's on a fresh
ledger, byte for byte; the GET listing marks it `notLoaded { code:
OS_PROTOCOL_INCOMPATIBLE, requiredRange: ^17 }`; lit control: a lifted
`^18` entry rehydrates with no error and registers the body deep-equal
to the ledgered one.
- `marketplace-install-local-sample-data-not-loaded.test.ts`, new group:
precondition (lifted refused not loaded, lifted loadable loaded); reseed
and purge on the lifted refused entry answer 422, leave i18n,
seed-datasets, the engine and the ledger unchanged, and their payloads
equal the flat entry's byte for byte; lit control: both doors answer 200
on the lifted loadable entry.
- `marketplace-install-local-rehydrate-order.test.ts`: a lifted module
entry listed ahead of its parent registers after it.
**Red on base** (plugin source swapped to `origin/main` `0b6a52e8`; on
disk: flatten-at-judge count 0, base judge count 2; restored with `git
checkout HEAD --`, `git diff HEAD` empty, blob hash equal to HEAD): 5
failed / 32 passed of 37, exactly the five lifted-shape cases above that
judge or order; both lit controls pass on both trees. **Green** on head:
37/37.
**Ablation**, one leg per site, through `scripts/ablation-replace.mjs`
(anchor 1 → 0 on disk, restored to the HEAD blob, `git diff HEAD` empty
after each). The tests import the plugin by relative path (source), so
there is no dist leg.
| Leg | Mutation | Red |
|---|---|---|
| 1 | rehydrate judges `entry.manifest` | 3: the two boot cases + the
sample-data precondition |
| 2 | door judges `entry.manifest` | 1: reseed/purge byte parity |
| 3 | order reads `entry.manifest` | 1: the lifted dependency order |
## Acceptance notes
- **Pre-existing, not changed:** a ledger file whose `manifest` is
`null` throws `Cannot read properties of null (reading 'engines')` from
`checkProtocolCompat`, at the rehydrate (logged as "rehydrate failed")
and out of the reseed/purge handlers. Identical on base and head. No
door or producer writes such an entry, so it is noted here and not
filed.
- Overlap: re-read the 17 open PRs before `pr_create`; none touches the
plugin file or the three test files.
## Verification
All on head `365d3ac3` (the only commit; the base is `origin/main`
`0b6a52e8`).
- `pnpm --filter @objectstack/cloud-connection test`: 65 files / 796
tests passed. `pnpm --filter @objectstack/cloud-connection typecheck`:
exit 0 (under the verify lock, `VERDICT command-exit 0`).
- The three touched files alone: 37/37.
- **Full dogfood battery** (owed by the `(narrowing)` arm), closure
built then `pnpm --filter @objectstack/dogfood test` under the lock:
`VERDICT command-exit 0` (held 2009s) · Test Files 369 passed, 3 skipped
(372) · Tests 2981 passed, 28 skipped (3009) · no failures.
- **Derived gates:** `dispatch-gates --commands --repo
objectstack-ai/objectstack` derived 60 families; all 60 exit 0, exit
codes captured before any pipe. `--ran`: "60 derived famil(ies)
accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded
an exit code and none of them is 3)". Two first runs were not
measurements and were re-run to exit 0: `check-plugin-teardown-shape
--self-test` exited 3 on the shallow clone until its pinned fixture
commit was fetched, and `check:dual-build-cjs-loads` exited 3 until a
full `packages/**` build. Record: project file
`pm-orders/server/evidence/28604/gates-ran-365d3ac3.txt`.
- CI families (lint, Test Core shards and the rest) are read from this
PR's check-runs, not re-run locally.
- Non-object ledger probe (base vs head byte-identical):
`pm-orders/server/evidence/28604/non-object-ledger-probe.txt`.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (CI).
- [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 维护者速读 section is
the lane's required maintainer summary).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5piHvtwq5wxJ2RJYfj
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5piHvtwq5wxJ2RJYfj)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent c946cb9 commit a06feb7
5 files changed
Lines changed: 167 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
609 | 609 | | |
610 | 610 | | |
611 | 611 | | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
612 | 621 | | |
613 | 622 | | |
614 | | - | |
| 623 | + | |
615 | 624 | | |
616 | 625 | | |
617 | 626 | | |
| |||
1093 | 1102 | | |
1094 | 1103 | | |
1095 | 1104 | | |
1096 | | - | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
1097 | 1110 | | |
1098 | 1111 | | |
1099 | 1112 | | |
| |||
1168 | 1181 | | |
1169 | 1182 | | |
1170 | 1183 | | |
1171 | | - | |
1172 | | - | |
1173 | | - | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
1174 | 1190 | | |
1175 | 1191 | | |
1176 | 1192 | | |
| |||
3833 | 3849 | | |
3834 | 3850 | | |
3835 | 3851 | | |
3836 | | - | |
3837 | | - | |
| 3852 | + | |
| 3853 | + | |
| 3854 | + | |
3838 | 3855 | | |
3839 | 3856 | | |
3840 | 3857 | | |
| |||
3843 | 3860 | | |
3844 | 3861 | | |
3845 | 3862 | | |
3846 | | - | |
| 3863 | + | |
3847 | 3864 | | |
3848 | 3865 | | |
3849 | 3866 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
177 | | - | |
178 | | - | |
| 177 | + | |
| 178 | + | |
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
| |||
432 | 432 | | |
433 | 433 | | |
434 | 434 | | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
435 | 488 | | |
436 | 489 | | |
437 | 490 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
97 | 109 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
350 | 350 | | |
351 | 351 | | |
352 | 352 | | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
353 | 414 | | |
354 | 415 | | |
355 | 416 | | |
| |||
0 commit comments