Skip to content

Commit 15e61fb

Browse files
os-zhuangclaude
andauthored
fix(metadata-protocol): publishPackageDrafts 对 api draft 跑 ADR-0121 端点发布门 (#5206 step 2) (#5279)
* fix(metadata-protocol): run the ADR-0121 endpoint publish gates on `api` drafts in publishPackageDrafts (#5206 step 2) `protocol.publishPackageDrafts` — the real entry point behind Studio's "publish everything" (ADR-0033 / ADR-0067 D2) — had exactly one type-aware pre-flight: the object namespace-prefix rule, gated on `d.type === 'object'`. An `api` draft was therefore promoted draft→active having met no gate at all, the same shape #5189 closed on `MetadataManager.publishPackage`, one path over. The gate reuses `validateApiEndpointDeclarations` (public from `@objectstack/spec/api` since #5203) verbatim — the same function the stack schema runs, the same function `publishPackage` runs, and the same `firstFailure` the endpoint matcher's load-time backstop runs. Refusal messages are the gate's own; nothing here restates a criterion for what is servable. Unlike `publishPackage`, this path already resolves the package's declared `manifest.namespace` for the object-prefix rule, so the FULL gate runs, namespace included (ADR-0121 D1/D2). Failure posture mirrors the pre-existing namespace-prefix rule exactly: a pre-flight refusal found before anything is promoted, whole batch withheld (`success: false`, `publishedCount: 0`, `published: []`), one entry per offending item in `failed[]`. Both violation classes now come back in one report instead of one class per publish attempt. The #5203 load-time backstop is untouched — this is the earlier door, not a replacement for the last one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7 * chore(scripts): record the #5206 endpoint-gate test's engine double as measured DEBT `check:engine-double-contract` flagged the fake engine in protocol-publish-drafts-endpoint-gate.test.ts as unpinned. The prescribed fix — a devDependency on @objectstack/objectql so the fake's delete can route through assertEngineDeleteDispatch — was run to completion rather than assumed: @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies`, so the reverse edge makes turbo refuse the build graph (`Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build`). The edge and lockfile were reverted. That is the same cycle, measured the same way, as the #4867 and #4981 entries already in this ledger, whose `closes` both point at #4987 (sink the predicate into a package both sides already depend on). This is the sixth metadata-protocol file to hit a route those entries record as closed. The entry is DEBT, not EXEMPT. It records that the double is structurally looser than the contract; it does not claim the looseness is currently harmless. The probe finding — delete is exercised, and only as a scalar `{ where: { id } }` — is recorded in `why` as a fact about this file, which is explicitly not an argument about the contract, per this ledger's own rule. Purely additive: one entry, no existing entry reordered, reworded or lowered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent db2ea82 commit 15e61fb

4 files changed

Lines changed: 708 additions & 11 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): `publishPackageDrafts` 现在对 `api` draft 跑 ADR-0121 端点发布门 (#5206 step 2)
6+
7+
`protocol.publishPackageDrafts` 是 Studio「全部发布」的真实入径(ADR-0033 /
8+
ADR-0067 D2)。在此之前,它唯一的按类型前置检查是对象命名空间前缀
9+
(`validateObjectNamespacePrefix`,仅 `d.type === 'object'`),于是一条 `api`
10+
draft **不经任何一道门**就被提升为 `active` —— 与 #5189
11+
`MetadataManager.publishPackage` 上修掉的是同一形状、另一条路。
12+
13+
安全后果早已被 PR #5203 的装载期兜底挡住:端点匹配器在建索引时用同一个
14+
`firstFailure` 重判每一条存量条目,没过门的被排除出索引并 `error` 点名。所以
15+
这次修的是**拒绝得太晚**:ADR-0121 的原文是「publish 拒绝」,作者应当在
16+
publish 当场拿到点名 key 的处方,而不是到装载期日志里才发现自己的端点在答
17+
404。
18+
19+
**判据只有一份。** 本改动调用 `@objectstack/spec/api` 导出的
20+
`validateApiEndpointDeclarations`(#5203 公开)—— 就是 stack schema 跑的那个
21+
函数、`publishPackage` 跑的那个函数、装载期兜底跑的那个 `firstFailure`。拒绝
22+
文案直接用门函数自己的消息(已包含端点名、越界的 key 和改法),本包不复述任何
23+
一条「什么算可服务」的规则。
24+
25+
`publishPackage` 不同,这条路**有身份**:包的 `manifest.namespace` 本来就
26+
为对象前缀规则读过了,所以这里跑的是**全量门**,命名空间门(ADR-0121 D1/D2)
27+
包含在内。命名空间门****以「包声明了 namespace」为条件 —— 门函数自己的前置
28+
判据(声明了 `apis:` 的 stack 必须显式声明 `manifest.namespace`)本身就是一条
29+
判据,对「压根没有 namespace」的包跳过它,等于给最不可能过编译期的那批包留一
30+
个洞。对象前缀规则对无 namespace 的包网开一面,是因为一个裸对象名只是命名气味;
31+
一个无命名空间的端点是一个**无主 URL**
32+
33+
**行为变化(用户可见)**:
34+
35+
- 一条 `api` draft 若违反端点门(最典型:ADR-0121 D6 —— `authRequired: false`
36+
却没有 `rateLimit.enabled: true` 的预算),`publishPackageDrafts` 现在返回
37+
`success: false` / `publishedCount: 0`,该条目进入 `failed[]`,`code`
38+
`ENDPOINT_GATE`;body 连 `ApiEndpointSchema` 都不满足的,`code`
39+
`ENDPOINT_SCHEMA`(解析是判定的前置,不是第六道门 —— 判不了的形状也服务不
40+
了)。
41+
- **失败粒度沿用既有语义,未发明新的批次语义**:与命名空间前缀违规完全一致,
42+
这是一次**提升任何东西之前**的前置拒绝,整批不落地(`published: []`),同批
43+
的健康 draft 保持 draft 态。这既是 ADR-0067 D2 的「一次 commit 不能落一半」,
44+
也是 #5189 在另一条路上的同一姿势(`itemsPublished: 0`)。两类违规现在合并
45+
**同一份报告**里返回,作者一次往返就能看全。
46+
- 判定范围是**本批被提升的 draft**,与紧邻它的对象前缀规则一致。与同包已
47+
`active` 的端点撞车不在此拦截 —— 匹配器对全库重复声明有确定性裁决并 `error`
48+
点名(`buildEndpointIndex`);把范围扩到整包 active 集合意味着「因为你没在发
49+
布的东西而拒绝这次发布」,那是另一份契约,不是一个 bug 修复。
50+
51+
装载期兜底(#5203)原样保留,未移除也未削弱:publish**更早**的那道门,不是
52+
最后那道门的替代品。
53+
54+
`api``DEFAULT_METADATA_TYPE_REGISTRY` / `BUILTIN_METADATA_TYPE_SCHEMAS`
55+
(即 Studio 直写路径的 422)是 #5206 的第 1 步,拆在子单 #5271(spec 车道);
56+
本改动**不依赖**它落地。

0 commit comments

Comments
 (0)