Repository navigation
Commit 462935d
feat(spec): declare the publish-drafts request body, so the change note is part of the published API contract (#28447)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
Fixes #28406
Clause-②: yes (widening)
Metadata-protocol: added
## 维护者速读
- **改了什么**:`POST /packages/:id/publish-drafts`(Studio「发布整个应用」)的请求体第一次写进了
`@objectstack/spec` 的公开契约:`PublishPackageDraftsRequestSchema` 声明
`message`(修改说明)和 `backfill`;路由台账这一行也写明了它。
- **为什么改**:#28422 让 Studio 发布时带上修改说明并写进每条历史记录,但这个字段只是服务端在读,契约里没有;SDK 和
API 用户看不到它。现在他们在类型和参考文档里都能看到。
- **风险与回滚**:线上行为一个字节都不变(服务端照旧读请求体);新契约不严格,旧 SDK 仍发送的 `actor`
被忽略、不会被拒。回滚即还原本 PR。
- **先看**:`packages/spec/src/api/protocol.zod.ts`(新 schema
与说明),`packages/runtime/src/domains/packages-publish-drafts-request-conformance.test.ts`(每个字段在服务端实测的行为),`packages/runtime/src/route-ledger.ts`(台账新增的
`requestSchema` 字段)。
- **两分钟验证**:`pnpm --filter @objectstack/runtime exec vitest run
src/domains/packages-publish-drafts-request-conformance.test.ts`,预期 8
个测试全过。
- **需要你做的**:无,席位落地。
## What changes
- **`@objectstack/spec/api`**: `PublishPackageDraftsRequestSchema` and
its type `PublishPackageDraftsRequest`, beside
`PublishPackageDraftsResponseSchema`. Two members, measured at the door
(`packages/runtime/src/domains/packages.ts`, publish-drafts branch), not
assumed:
- `message`: optional string. The door forwards it only when it is a
string.
- `backfill`: optional array of `{ object, field }`, both non-empty
strings, entries strict. No default: absent or `[]` asks for nothing.
The door forwards it as sent; the protocol's shape judgement
(`readPublishBackfillRequest`) refuses a malformed entry `400
VALIDATION_ERROR` before anything is published. The "named once" and
"field is new and has a literal default" rules are described, not
encoded: a refinement would be dropped from the published JSON Schema,
and the second is judged against server state.
- The object is **not strict**: unknown members are stripped, not
refused. `actor` is deliberately not declared (the door records the
authenticated caller), and `@objectstack/client`'s
`packages.publishDrafts` still sends its deprecated `opts.actor`, so a
strict object would describe that call as invalid.
- **Route ledger** (`packages/runtime/src/route-ledger.ts`): the
dispatcher ledger had no `requestSchema` field (only `responseSchema`).
The field is added, runtime-internal like the rest of the ledger, with
the same rules as `responseSchema` (a name, resolved against the spec
exports, never filled without conformance coverage). Its JSDoc says what
a filled row claims and what it does not. The publish-drafts row names
`PublishPackageDraftsRequestSchema`.
- **Resolver guard**
(`packages/client/src/route-ledger-response-schema.test.ts`): ledgered
`requestSchema` names resolve against the live spec exports, using the
existing resolver, with an anti-vacuity pin.
- **Regenerated by repo tooling**: `api-surface`, `export-origins`,
`declaration-map`, `authorable-surface`, `json-schema.manifest`,
`content/docs/references/**`, strictness-ledger counts. The SDK client
is hand-written, not generated, and `packages/client/src/index.ts` is
not touched. `gen:openapi` emits a fixed list of nine contract schemas
and its output is gitignored, so nothing there moves.
- Changeset: `@objectstack/spec` minor.
## Tests
- `packages/spec/src/api/protocol.test.ts`: the schema accepts `{}`, `{
message }`, `{ backfill }` and both; strips the SDK's `actor`; refuses a
non-string `message` and each malformed `backfill` shape with the zod
issue code and path; declares exactly the two members.
-
`packages/runtime/src/domains/packages-publish-drafts-request-conformance.test.ts`
drives the real handler:
- each schema-accepted body reaches `publishPackageDrafts` exactly as
the schema parses it; an absent body reads as `{}`;
- a body `actor` gets 200 and never reaches the publish (the session
user is recorded);
- `aiModel`, `context` and `packageId` in the body are not read;
- each malformed `backfill` is refused `400 VALIDATION_ERROR` naming the
entry, through the real `ObjectStackProtocolImplementation` (its engine
is a proxy that throws if touched, which shows the judgement runs before
any read);
- the ledger row names the schema.
- `packages/spec/src/type-alias-convention.pin.test.ts`: the isomorphism
pin for the new schema (805 → 806).
## Acceptance notes
- **The door does not parse the body with the schema.** A non-string
`message` is dropped and the publish goes ahead with no note: 200, not
400. The conformance suite pins this as a measured divergence, so a door
that starts enforcing the schema flips one pin. Changing the door is
outside this card's file surface
(`packages/runtime/src/domains/packages.ts`, which #28414 is editing
now). It is reported to the seat.
- **One undeclared read remains on `main`:** the ADR-0045 app re-save
after the publish still passes a body `actor` to `saveMetaItem`. #28414
removes that read. The conformance suite pins it today, and the pin's
message says what to flip when #28414 lands.
- #28414 had no open PR when this was opened, and its branch touches no
`packages/spec` file, the route ledger or generated artefacts.
## Verification
<details><summary>Local gates and tests (head sha in the os-dev-report
comment on the card)</summary>
Filled in the os-dev-report comment on #28406: `dispatch-gates
--commands` families with exit codes, `--ran` reconciliation, and the
reverse verification of the non-strict choice.
</details>
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 776c1d8 commit 462935d
15 files changed
Lines changed: 428 additions & 15 deletions
File tree
- .changeset
- content/docs/references
- api
- docs/audits/2026-07-unknown-key-strictness-ledger.counts
- packages
- client/src
- runtime/src
- domains
- spec
- api-surface
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- 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 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
| 95 | + | |
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| |||
| 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 | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| |||
185 | 185 | | |
186 | 186 | | |
187 | 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 | + | |
0 commit comments