Repository navigation
Commit bd5a443
fix(metadata-protocol): a refused targetNamespace answers 400 INVALID_REQUEST with its reason (#31182)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiU1rhbHT2BKdngdowSzudqP)_
Part of #30794
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:unmanaged 安装时填错 `targetNamespace`(不合命名规则,或填了共享的
`base`/`system`/`sys`),现在回 `400 INVALID_REQUEST` 并说明原因;另加测试钉住 Q6 =
B(改名副本旁边装 managed 版本放行,未改名副本旁边仍 409 拒绝)。
- 为什么改:之前这两处回 `VALIDATION_ERROR`,而 install-local
接口只回「原因在服务器日志里」,客户看不到填错了什么;文档写的一直是 `INVALID_REQUEST`。
- 风险与回滚:只改两个拒绝的错误码和附带的 `details`,拒绝时机不变、仍在任何写入之前;回滚即 revert 本
PR。`targetPackageId` 的拒绝没动。
- 先看:`packages/metadata-protocol/src/package-unmanaged-install.ts`(两处
`refusal('INVALID_REQUEST',
…)`)、`packages/cloud-connection/src/marketplace-install-local-install-modes.test.ts`(末尾两个新
describe)。
- 两分钟验证:`pnpm --filter @objectstack/cloud-connection exec vitest run
src/marketplace-install-local-install-modes.test.ts`,预期 34 个全过,其中 4 个是本
PR 新加的。
- 需要你做的:无,席位落地。
## What changes
- `planUnmanagedImport`
(`packages/metadata-protocol/src/package-unmanaged-install.ts`): the two
`targetNamespace` refusals (a value `ManifestSchema.shape.namespace`
refuses; a platform-reserved namespace `base`, `system`, `sys`) now
throw `refusal('INVALID_REQUEST', 400, …, { targetNamespace })` instead
of a code-less `statusCode: 400`. That is the code the install-modes
guide's refusal row and PR A's changeset already state, and the code the
sibling rename refusal (`unmanagedInstallRenameRefusal`) answers.
- The install-local door now passes the reason through. Before,
`clientFacingFailureText` withheld the message of a `statusCode`-only
400, so the door answered `{"code":"VALIDATION_ERROR","message":"The
unmanaged install of 'com.example.crm' failed; the reason is in the
server log."}`. Now it answers `400
{"code":"INVALID_REQUEST","message":"Invalid package namespace 'Acme' on
`targetNamespace`. …","details":{"targetNamespace":"Acme"}}`, and no log
line is written because the caller has the reason.
- Q6 = B pins (ruling record 6709037798): a managed install of
`com.example.crm` beside a copy renamed to `acme` proceeds to its own
steps; its twin, beside a copy that kept `crm`, is refused `409
RESOURCE_CONFLICT` before any write. Step 1f
(`findEnvironmentBaseHoldingNamespace`) is untouched.
- One changeset, `@objectstack/metadata-protocol` patch.
`@objectstack/cloud-connection` changes only a test file, so it
publishes nothing new.
## Tests
- `packages/metadata-protocol/src/package-unmanaged-install.test.ts`:
the two planner pins move from `[400, 'VALIDATION_ERROR']` to `[400,
'INVALID_REQUEST']` and assert `details` is `{ targetNamespace }`.
-
`packages/cloud-connection/src/marketplace-install-local-install-modes.test.ts`,
two new top-level `describe`s in the existing harness:
- the door with the REAL
`ObjectStackProtocolImplementation.importUnmanagedPackage` (not a
double): `Acme` and `sys` each answer 400 `INVALID_REQUEST` with the
reason in the body and `details.targetNamespace`, nothing registered, no
ledger entry;
- the Q6 pair described above.
## Acceptance notes
- **Left alone on purpose:** the `targetPackageId` refusals (id rule,
same id as the source) keep their code-less 400 and still answer
`VALIDATION_ERROR`. Changing them is a separate call, as the hand-over
(6690317826) says. The non-string `targetNamespace` refused by the
request's published declaration (`ImportUnmanagedPackageRequestSchema`)
also stays `VALIDATION_ERROR` with `issues`; that is a schema refusal,
not the plan's.
- **Docs:** no sentence became false. `install-modes.mdx` already says
`400 INVALID_REQUEST, naming targetNamespace`, and "a managed install of
the package is refused while the copy holds its namespace" agrees with
Q6 = B (a renamed copy does not hold it). PR B states Q6 in ADR-0131 D6.
## Verification
<details><summary>Local tests (head 9aa77b4)</summary>
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2 src/package-unmanaged-install.test.ts`: Test Files 1
passed, Tests 82 passed.
- `pnpm --filter @objectstack/cloud-connection exec vitest run
--maxWorkers=2 src/marketplace-install-local-install-modes.test.ts`:
Test Files 1 passed, Tests 34 passed.
</details>
<details><summary>Ablation (door pin)</summary>
`node scripts/ablation-replace.mjs` turned the reserved-namespace
refusal back into the code-less `invalidRequest(…)` (anchor 1 → 0, blob
eb17407 → 23f7fd3), then rebuilt `@objectstack/metadata-protocol` and
ran the door pin: `× a platform-reserved namespace: 400 INVALID_REQUEST
…`, Expected `"INVALID_REQUEST"`, Received `"VALIDATION_ERROR"` (1
failed, 1 passed). Restored (blob == HEAD, `git diff HEAD` empty),
rebuilt, and the file passed 34/34.
</details>
<details><summary>Derived gates (head 9aa77b4): 60 derived, 59 exit 0,
1 NOT MEASURED</summary>
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (4 paths vs merge base 8ef4d30), run under
`NODE_OPTIONS=--max-old-space-size=8192`, exit codes captured before any
pipe, reconciled with `--ran`: "60 derived famil(ies) accounted for — 59
run, 1 NOT-MEASURED".
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
- `pnpm check:dual-build-cjs-loads` exit 3 = PREREQUISITE NOT MET (no
whole-tree `pnpm build` in this container; its self-test passed 93
cases). NOT MEASURED locally; CI's `Lint & Repo Gates` runs it.
- `pnpm lint` :: exit 0.
- `pnpm --filter @objectstack/metadata-protocol --filter
@objectstack/cloud-connection typecheck` :: exit 0.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2ewD2rAszkPvzKBT8T)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8acbfab commit bd5a443
4 files changed
Lines changed: 89 additions & 6 deletions
File tree
- .changeset
- packages
- cloud-connection/src
- metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
567 | 568 | | |
568 | 569 | | |
569 | 570 | | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
954 | 954 | | |
955 | 955 | | |
956 | 956 | | |
957 | | - | |
| 957 | + | |
958 | 958 | | |
959 | 959 | | |
960 | 960 | | |
961 | | - | |
| 961 | + | |
| 962 | + | |
962 | 963 | | |
963 | 964 | | |
964 | 965 | | |
965 | 966 | | |
966 | | - | |
| 967 | + | |
967 | 968 | | |
968 | 969 | | |
969 | 970 | | |
970 | | - | |
| 971 | + | |
| 972 | + | |
971 | 973 | | |
972 | 974 | | |
973 | 975 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
480 | 480 | | |
481 | 481 | | |
482 | 482 | | |
483 | | - | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
484 | 490 | | |
485 | 491 | | |
| 492 | + | |
486 | 493 | | |
487 | 494 | | |
488 | 495 | | |
489 | | - | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
490 | 499 | | |
491 | 500 | | |
492 | 501 | | |
| 502 | + | |
493 | 503 | | |
494 | 504 | | |
495 | 505 | | |
| |||
0 commit comments