Repository navigation
Commit aad1603
fix(spec): PackageInstallResponseSchema declares the bare installed row the install door serves (#30593)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuV5cRafodM2YQHxeDkdiWJM)_
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Fixes #30464
Clause-②: yes (narrowing) — `PackageInstallResponseSchema` now accepts
the bare installed row the door serves, which it refused, and refuses
the `{ package }` wrapper it accepted; `PackageInstallResponse['data']`
loses `package`, `dependencyResolution`, `namespaceConflicts` and
`message`. No export is added, removed or renamed.
Metadata-protocol: none — the shape of an API answer, not anything a
metadata author writes.
Skills-docs: none — `git grep -nE
'PackageInstallResponse|data\.package\b' -- skills/ content/docs/
':!content/docs/references'` has 0 hits; the `data.package` reads in
`packages/` and `ui/` source are other answers (override declarations,
the permission-set clone, the marketplace detail), none the install
answer.
## 维护者速读
- 改了什么:已发布的 API 契约里「安装包」接口(`POST
/api/v1/packages`)的应答形状改成服务器实际返回的样子:`data` 就是安装后的包记录本身,不再声明一个从未发送过的 `{
package, … }` 外层包装。服务器和官方客户端的行为都不变。
- 为什么改:照着契约写集成代码的人会读 `data.package`,实际拿到
`undefined`;官方客户端早已按真实返回改过(#12034),只剩已发布契约是错的。依赖它的 #30412
要在这个形状上决定安装门是否加提示。
- 风险与回滚:只改 spec 里一个应答 schema 和由它生成的文档,服务器代码不动。按旧契约读 `data.package`
等四个成员的 TypeScript 代码升级后会编译报错(这些代码运行时本来就读到 undefined),changeset
写明了改法。回滚即撤本 PR。
- 先看:`packages/spec/src/api/package-api.zod.ts` 的
`PackageInstallResponseSchema`(`data`
那一行);`packages/client/src/packages-write-envelope.test.ts` 的 `#30464`
块(真实安装门的应答原样交给契约解析);`.changeset/30464-install-response-bare-row.md`。
- 两分钟验证:`pnpm --filter @objectstack/client exec vitest run
src/packages-write-envelope.test.ts`,预期 6 条全绿;把 `data` 换回 main 上的包装(连同
`dropped-refinements.baseline.json` 里那三行)并重建 spec 后,`#30464` 块的 2 条转红。
- 需要你做的:无,席位落地。
## What changes
The published contract follows the wire; the door is not changed. Triage
audit 6658640900: "The published contract is wrong, not the door … The
change corrects the published schema to match the wire".
- **`packages/spec/src/api/package-api.zod.ts`**:
`PackageInstallResponseSchema.data` is `InstalledPackageSchema`, reused
rather than restated: the `InstalledPackage` row
`SchemaRegistry.installPackage` recorded, which the door answers as
`success(pkg)` with status `201` on both install arms. The docblock says
why, names the authoring stage of the row (this door computes no
`writable` / `disableable`), and names the pin. The type names
`PackageInstallResponse` / `PackageInstallResponseParsed` and the
`PackageApiContracts.installPackage` binding are unchanged.
- **`packages/spec/src/api/package-api.test.ts`**: the two cases that
pinned the wrapper are replaced by three: the bare row parses; the `{
package }` wrapper is refused at `data.manifest` and `data.source`;
`PackageApiContracts.installPackage.output` is this schema.
- **`packages/client/src/packages-write-envelope.test.ts`**: a
contract-vs-wire block. A real `SchemaRegistry` and the real
`HttpDispatcher.handlePackages('', 'POST', …)` produce the 201 body,
which goes untouched to
`PackageApiContracts.installPackage.output.safeParse`: it parses green,
`data` equals the registry's row and the parse strips no key the door
sent; the wrapper built from the door's own row is refused. Nothing in
the block writes the expected body by hand.
- **`packages/spec/dropped-refinements.baseline.json`**: the three
`api/PackageInstallResponse` sites move from `data.package.manifest.…`
to `data.manifest.…`: the same three refinements, their path moved with
the shape (the spec build gates this ledger).
- **Regenerated**: `content/docs/references/api/package-api.mdx`;
`docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md`
(`api/` 501 → 499 sites: the wrapper object and the conflict-entry
object are gone).
- **Changeset** (`@objectstack/spec`: `patch`) with both declaration
lines, the FROM → TO mapping, the consumer fix and the ADR-0087
disposition `not-required (no-migration-prescription)`.
## Tests
Head `3789cc39e` (this branch merged with `origin/main` at `c23eafa88`).
<details><summary>Package suites, typecheck, eslint</summary>
- `pnpm --filter @objectstack/spec test`: Test Files 815 passed (815),
Tests 25644 passed, 1 todo.
- `pnpm --filter @objectstack/client test`: Test Files 54 passed (54),
Tests 699 passed. The pin file alone: 6 passed (6).
- `pnpm --filter @objectstack/runtime test`: Test Files 543 passed
(543), Tests 8655 passed, 19 skipped. The suites that drive the install
door (`handlePackages('', 'POST', …)`): the eight
`packages/runtime/src/domains/packages-install-*.test.ts`,
`packages-ensure-customization.test.ts`, `http-dispatcher.test.ts`.
- `pnpm --filter @objectstack/spec run typecheck`: exit 0 (test layer:
52 files / 240 errors / 131 signatures held in the existing debt ledger,
unchanged). `pnpm --filter @objectstack/client run typecheck`: exit 0
(test layer 0 / 0 / 0).
- eslint (`--no-inline-config`) over the three changed TypeScript files:
exit 0, no findings.
</details>
<details><summary>Dogfood battery (owed by the narrowing arm)</summary>
`pnpm --filter @objectstack/dogfood test` under the verify lock, after a
full workspace build (`turbo run build`, 119/119 tasks): `VERDICT
command-exit 0`; Test Files 420 passed, 3 skipped (423); Tests 3496
passed, 36 skipped (3532); no failure.
</details>
<details><summary>Ablation: main's wrapper put back on `data`</summary>
- Mutation: `scripts/ablation-replace.mjs` replaced the `data:` line
with main's `{ package, dependencyResolution?, namespaceConflicts?,
message? }` object (anchor 1 → 0, marker `ABLATION_30464`) and moved the
three ledger sites back to `data.package.manifest.…` (anchor 3 → 0): a
two-file mutation, because the spec build gates
`dropped-refinements.baseline.json`. Spec rebuilt;
`ablation-dist-preflight` read the marker in 2 built files.
- Under the mutation: client pin 2 failed, 4 passed (6), exactly the two
`#30464` cases; spec pin 2 failed, 96 passed (98), the bare-row and
wrapper-refusal cases.
- Restore: both paths back to their HEAD blobs, `git diff HEAD` empty;
spec rebuilt; preflight `--absent` (marker absent from all 592 built
files, tree clean). Client pin 6 passed (6); spec pin 98 passed (98).
- The first mutation attempt is void: it moved only the schema, the spec
build refused on the ledger, the preflight read the marker ABSENT from
`dist/`, and its client reading was discarded.
</details>
## Acceptance notes
- **#30412 depends on this shape.** That card (`domain:server#6`) has
the install door answer an optional `protocolCheckSkipped` notice, as
the install-local door does. With this PR the declared `data` is the
bare `InstalledPackage` row, so where that notice sits (on the envelope
beside `data`, or elsewhere) is decided on that card against this shape;
there is no wrapper left to add a member to. `packages/runtime/**` is
untouched in this PR.
- **Declarations changed from the claim.** The claim carries `Clause-②:
yes` and `Metadata-protocol: semantic`. Measured and read against the
rules, this PR declares `yes (narrowing)` and `none`, at `patch`:
`semantic` is "the meaning or default of an authorable key", and this
schema is an API answer; the narrowing half (the wrapper refused, four
type members gone) is real on the published `@objectstack/spec/api`
surface. Both pairs pass `check-changeset-no-major` and
`check:adr-0087-registration`, which do not judge the act; `none` with
`minor` exits 1 (`none-minor`). The seat updates the claim lines.
- **ADR-0087 vocabulary gap, noted.** `type-surface-only` is closed to a
narrowing whose schema lives in `packages/spec` (predicate 2,
no-spec-diff), and `runtime-interface-only` to a `*.zod.ts`, so a spec
API-answer narrowing whose affected party is a TypeScript consumer has
no exact category. It carries `no-migration-prescription`, which the
gate admits because its detector reads no arrow or framed table in the
consumer line; the marker says so and cites D8.
- **`InstallPackageResult`**
(`packages/spec/src/contracts/package-service.ts`) still declares `{
package, dependencyResolution?, namespaceConflicts?, message? }`. It is
the in-process `IPackageService` result, not the HTTP answer, and this
PR does not touch it.
## Verification
Head `3789cc39e`.
<details><summary>Derived gate families: 102 run, reconciled</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 102 commands for 7 paths vs merge base
`c23eafa88`. Each was run with its exit code captured before any pipe;
`--ran` answers "102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN" (a
derived zero: all 102 carry an exit code).
- First pass: 100 exit 0, 2 exit 3 (PREREQUISITE NOT MET, nothing
measured). `check-plugin-teardown-shape --self-test` needed a
positive-control commit this shallow clone lacked: fetched at depth 1,
rerun exit 0 (48 cases). `check:dual-build-cjs-loads` needed 32 more
packages' `dist/`: full build, rerun exit 0 (116 entries across 71
packages load).
- `check:skill-examples`: the first run exits 3, PREREQUISITE NOT MET,
because `packages/client-react/dist` held no `.d.ts`. After `pnpm
--filter @objectstack/client-react build`: exit 0, "283 prose examples
type-check across 3 surface(s)".
- `pnpm --filter @objectstack/spec check:generated`: all 14 generated
artifacts up to date.
- Local-only families (spec build touched): `check:dev-prereqs` exit 0
after the full build (107 artifacts present);
`check:skill-frame-freshness` exit 0.
</details>
<details><summary>Declaration gates</summary>
- `check-changeset-no-major.mjs --base origin/main --event` with a
`pull_request` payload carrying this body's lines: exit 0, "LEVEL AXIS:
… a non-protocol change at `patch`".
- `check-adr-0087-registration.mjs --base origin/main`: exit 0, "1
declared-breaking changeset(s), each carrying an ADR-0087 disposition …
[clause-②-narrowing] not-required (no-migration-prescription)".
- Control: the claim's pair (`yes` / `semantic` / `minor`) also exits 0
on both gates, and `none` beside the old `minor` exits 1 (`none-minor`).
The gates check the declarations against the levels; which pair is true
is read from the diff (Acceptance notes).
</details>
<details><summary>Outside the derived total</summary>
- Four CI-only families take a value from the workflow (`$RUNNER_TEMP`,
the shard matrix) and have no local invocation:
`check-issue-citations.mjs --census`, `check-shard-attestation.mjs`
(dogfood, dogfood-verify) and `check-test-completeness.mjs`. NOT
MEASURED locally; read on this PR's check-runs.
- `origin/main` moved 15 commits past the merge base (to `48b3ae6eb`)
after the merge. None touches this diff's files or the install answer:
the two inside its packages are the seed-apply catch in
`packages/runtime/src/domains/packages.ts` and the seed `landed`
describe in `packages/spec/src/api/protocol.zod.ts`.
</details>
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6701c08 commit aad1603
7 files changed
Lines changed: 152 additions & 65 deletions
File tree
- .changeset
- content/docs/references/api
- docs/audits/2026-07-unknown-key-strictness-ledger.counts
- packages
- client/src
- spec
- src/api
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
461 | 461 | | |
462 | 462 | | |
463 | 463 | | |
464 | | - | |
| 464 | + | |
465 | 465 | | |
466 | 466 | | |
467 | 467 | | |
| |||
490 | 490 | | |
491 | 491 | | |
492 | 492 | | |
493 | | - | |
494 | | - | |
495 | | - | |
496 | | - | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
497 | 506 | | |
498 | 507 | | |
499 | 508 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
71 | 74 | | |
72 | 75 | | |
73 | 76 | | |
| |||
232 | 235 | | |
233 | 236 | | |
234 | 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
593 | 593 | | |
594 | 594 | | |
595 | 595 | | |
596 | | - | |
597 | | - | |
598 | | - | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
599 | 599 | | |
600 | 600 | | |
601 | 601 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
211 | | - | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | | - | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
225 | 217 | | |
226 | | - | |
227 | | - | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
228 | 222 | | |
229 | 223 | | |
230 | | - | |
231 | | - | |
| 224 | + | |
| 225 | + | |
232 | 226 | | |
233 | | - | |
234 | | - | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | | - | |
247 | | - | |
| 227 | + | |
248 | 228 | | |
249 | | - | |
250 | | - | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
251 | 236 | | |
252 | 237 | | |
253 | 238 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
522 | 522 | | |
523 | 523 | | |
524 | 524 | | |
525 | | - | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
526 | 551 | | |
527 | 552 | | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
| 553 | + | |
541 | 554 | | |
542 | 555 | | |
543 | 556 | | |
| |||
0 commit comments