Repository navigation
Commit 12333ba
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XAZVBCz7kire8DBAJx1hUc4)_
Fixes #28452
Clause-②: no (narrowing) — a body whose `message` is not a string (a
number, an object, an array, `null`), which `POST
/packages/:id/publish-drafts` answered 200 with the default note, is
refused 400 `VALIDATION_ERROR` before anything is published. Nothing is
widened.
Metadata-protocol: none
Skills-docs: none — no page under `skills/**` or `content/docs`
describes what the door does with a non-string note; the generated
reference (`content/docs/references/api/protocol.mdx`) already declares
`message` a string from the schema's `describe`, which does not move.
**BREAKING** accept-set narrowing at the publish-drafts door
(`@objectstack/runtime`).
## 维护者速读
- **改了什么**:「整包发布草稿」接口(`POST /packages/:id/publish-drafts`)现在用规范里已声明的
`PublishPackageDraftsRequestSchema` 的 `message`
成员校验变更说明;不是字符串(数字、对象、数组、null)时直接 400 `VALIDATION_ERROR` 拒收并点名
`message`,什么都不发布。另翻转 #28447 留下的「已知分歧」测试针、路由台账那行的说明和 spec 注释。
- **为什么改**:卡 #28452(分诊
6547072197):以前非字符串的说明被悄悄丢掉,发布照常进行、每条历史记录写默认说明,调用方根本不知道自己的说明被忽略了。
- **风险与回滚**:只收窄这一种请求体;字符串说明(包括空串)、不带说明、带旧 SDK 的 `actor` 都照旧 200;坏的
`backfill` 仍由协议层拒一次,答复与以前完全一样。仓内没有任何调用方发送非字符串说明(SDK 与 UI 只在给了字符串时才带
`message`)。回滚即 revert。
- **先看**:`packages/runtime/src/domains/packages.ts`(publish-drafts
分支里新加的 `declaredMessage`
校验与转发行);`packages/runtime/src/domains/packages-publish-drafts-change-note.integration.test.ts`(真实
SQLite 上的新用例);`.changeset/28452-publish-drafts-message-refused.md`。
- **两分钟验证**:`pnpm --filter @objectstack/runtime exec vitest run
src/domains/packages-publish-drafts-change-note.integration.test.ts
src/domains/packages-publish-drafts-request-conformance.test.ts` → 13/13
通过。
- **需要你做的**:无,席位落地(收窄需先过合约评审)。
## What changes
**The door** (`packages/runtime/src/domains/packages.ts`, the
`publish-drafts` branch). After the 404 for an unknown package and
before the publish, the door parses `body.message` with
`PublishPackageDraftsRequestSchema.shape.message` (the same
member-of-the-declaration read the install door does with
`ManifestSchema.shape.id` / `.version`). A note the member refuses is
answered with `deps.error(text, 400)`, the envelope the install door's
schema refusals already use (status 400 derives `VALIDATION_ERROR`; no
code is minted). The text names the path: `Invalid publish-drafts body —
message: Invalid input: expected string, received number. Nothing was
published: …`. The forwarded note is now the parsed value.
**What stays exactly as it was**, by measurement:
- `backfill` is not judged at the door. Parsing the whole body here
would refuse a bad `backfill` a second time with a different sentence
and without the protocol's `issues[]`, so the protocol's own shape
judgement stays the one refusal (same status, code, headline and
`issues[]`; the existing real-protocol case in the conformance suite
still passes with `publishPackageDrafts` called once).
- Unknown keys are not read: `{ actor: 'x', message: 'n' }` answers 200
and records the caller (SDK's deprecated `opts.actor`).
- A body that is not an object (or absent) has no `message` and
publishes with the default note, as before. Only the `message` member
narrows.
- ⛔ No `actor` handling changed (#28414 is closed; the card's Not-here
line).
**Pins and prose flipped.**
- `packages-publish-drafts-request-conformance.test.ts`: the
known-divergence case ("dropped by the door, not refused") becomes a
refusal case over `{message: 42}`, `{message: {}}`, `{message: []}`,
`{message: null}` and `{actor: 'x', message: 7}`: 400,
`VALIDATION_ERROR`, headline names `message`, `publishPackageDrafts` and
the app re-save never called. Its header's `message` bullet and the
"does not claim the door parses the body" sentence are updated.
- `packages-publish-drafts-change-note.integration.test.ts` (real door +
real protocol + ObjectQL + on-disk SQLite): `42`, `{}`, `[]` are each
refused 400 `VALIDATION_ERROR`; no `publish` history row and no package
commit exist afterwards; the same draft then publishes with a string
note and that row carries it.
- `route-ledger.ts`: the publish-drafts row note now says `message` is
parsed with the schema's member and a non-string is refused 400.
- `packages/spec/src/api/protocol.zod.ts`: the
`PublishPackageDraftsRequestSchema` docblock said "`message` — forwarded
only when it is a string"; this diff makes that false, so it now says
the door refuses a note the member refuses. JSDoc only: no `describe`,
key or type moves, so the generated reference and the authorable surface
do not move.
- The changeset carries a sentence superseding "Nothing changes on the
wire" in the pending `28406-publish-drafts-request-schema.md` changeset
(not edited: it is not this PR's).
## Tests
- **Red first** (the two new cases written and run against the unchanged
base door, before the fix was written): both new cases red with
`{"message":42}: expected 200 to be 400`; 11 other cases green.
- **Green** at the fix: the two files 13/13.
- **Ablation** (`node scripts/ablation-replace.mjs`, anchor `if
(!declaredMessage.success) {` → `if (false && !declaredMessage.success)
{`, which restores the string-only forward since a failed parse forwards
no `data`): anchor 1 → 0, blob `ba0e3795ad83` → `5906236be414`; both
refusal cases red, 11 controls (string note, no body, `actor` stripped,
undeclared members, bad `backfill` real-protocol refusal) green; restore
blob equals HEAD, `git diff HEAD` empty. The runtime tests import the
door from `src/`, so no build sits between the mutation and the run.
- `pnpm --filter @objectstack/runtime typecheck` exit 0; `pnpm --filter
@objectstack/runtime test`: Test Files 498 passed (498), Tests 8085
passed | 19 skipped (8104).
## Acceptance notes
- **Clause-② arm.** The order and the claim expected `yes (narrowing)`.
`scripts/pm/clause2-line.mjs` reads `yes (narrowing)` as "widens one
surface and narrows another"; this diff widens nothing, so the measured
line is `no (narrowing)`, with the `**BREAKING**` banner and the
ADR-0087 `not-required (no-migration-prescription)` marker in the
changeset.
- **File surface beyond the claim.**
`packages/spec/src/api/protocol.zod.ts` (the docblock sentence this diff
made false) and an `@objectstack/spec` patch line in the changeset. The
claim's file surface did not list them; the seat may amend it.
- **Placement.** The refusal sits after the 404 and the 501 capability
probe, beside the protocol's backfill judgement, so both body-member
refusals answer at one point of the door's order (the install door
orders its body legs the same way relative to its id gate).
## Verification
All readings on head `af581fe0` (the only commit; nothing landed after
the union ran).
- **Derived gate families:** `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` → 84 families, each run
with its exit code captured before any pipe; `--ran` reconciliation: `✓
dispatch-gates --ran: 84 derived famil(ies) accounted for — 84 run, 0
NOT-MEASURED`. Two first reads were container prerequisites, re-read
after meeting them: `check-plugin-teardown-shape.mjs --self-test` exit 3
→ `git fetch --depth=1 origin 621a487…` → exit 0 (48 cases pass);
`check:dual-build-cjs-loads` exit 3 → full `pnpm build --concurrency=2`
(116/116 tasks) → exit 0.
- **Hand gates:** `node scripts/check-platform-checklist.mjs
--changed-from origin/main` exit 0; `node
scripts/check-commit-card-trailers.mjs --range origin/main..HEAD` exit
0; `pnpm exec eslint --no-inline-config` over the five touched source
files exit 0 (neither `@objectstack/runtime` nor `@objectstack/spec` has
a `lint` script; the repo-wide `pnpm lint` is CI's).
- **Full dogfood** (owed by the `!` and the `(narrowing)` arm): after
the full build, under the lock, `pnpm --filter @objectstack/dogfood
test` → Test Files 363 passed | 3 skipped (366), Tests 2932 passed | 28
skipped (2960); no failure.
- **CI** families (Test Core, lint farm, type-check lanes) are read off
this PR's check-runs by the seat.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5kzB44oaLQ7JxbMKk9
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5kzB44oaLQ7JxbMKk9)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2c0a97b commit 12333ba
6 files changed
Lines changed: 99 additions & 17 deletions
File tree
- .changeset
- packages
- runtime/src
- domains
- 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 | |
|---|---|---|---|
| |||
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
163 | 183 | | |
164 | 184 | | |
165 | 185 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | | - | |
17 | | - | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
144 | 146 | | |
145 | 147 | | |
146 | 148 | | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
155 | 164 | | |
156 | 165 | | |
157 | 166 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| 153 | + | |
153 | 154 | | |
154 | 155 | | |
155 | 156 | | |
| |||
1914 | 1915 | | |
1915 | 1916 | | |
1916 | 1917 | | |
| 1918 | + | |
| 1919 | + | |
| 1920 | + | |
| 1921 | + | |
| 1922 | + | |
| 1923 | + | |
| 1924 | + | |
| 1925 | + | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
| 1933 | + | |
| 1934 | + | |
| 1935 | + | |
| 1936 | + | |
| 1937 | + | |
| 1938 | + | |
| 1939 | + | |
| 1940 | + | |
| 1941 | + | |
| 1942 | + | |
| 1943 | + | |
| 1944 | + | |
| 1945 | + | |
| 1946 | + | |
| 1947 | + | |
| 1948 | + | |
| 1949 | + | |
1917 | 1950 | | |
1918 | 1951 | | |
1919 | 1952 | | |
| |||
1924 | 1957 | | |
1925 | 1958 | | |
1926 | 1959 | | |
1927 | | - | |
1928 | | - | |
1929 | | - | |
| 1960 | + | |
| 1961 | + | |
1930 | 1962 | | |
1931 | 1963 | | |
1932 | 1964 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
523 | 523 | | |
524 | 524 | | |
525 | 525 | | |
526 | | - | |
| 526 | + | |
527 | 527 | | |
528 | 528 | | |
529 | 529 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1497 | 1497 | | |
1498 | 1498 | | |
1499 | 1499 | | |
1500 | | - | |
| 1500 | + | |
| 1501 | + | |
| 1502 | + | |
1501 | 1503 | | |
1502 | 1504 | | |
1503 | 1505 | | |
| |||
0 commit comments