feat(spec)!: 声明式 apis: 翻转 —— 硬拒收窄为逐端点门(#5040 E7) - #5188
Merged
Conversation
…ates (#5111, #5040 E7) THE FLIP. #4936 refused a non-empty `apis:` wholesale because the declarative endpoint surface executed nothing — no route mounted, no matcher, every key including `authRequired` parsed green and gated nothing. The #5040 E-series built the executor, so that premise is gone; keeping the refusal would be the lie in the other direction. This replaces the blanket `.max(0)` with a per-endpoint gate on `ObjectStackDefinitionSchema`, and an endpoint that passes it is MOUNTED and serves traffic on publish. Gates, each rejecting with a prescription naming the endpoint and the key: - namespace (ADR-0121 D1/D2): `path` must be `/api/v1/apps/<manifest.namespace>/<subpath>`; `manifest.namespace` must be declared explicitly (#5040 Q1 = A — no `deriveNamespaceFromPackageId` fallback for an outward URL contract); - supported subset (mirrors `planEndpointTarget`): `script` / `proxy`, an `object_operation` missing `objectParams.object|operation`, a `flow` with an empty `target`; - mapping (mirrors `mappingDeclarationRejection`): any `transform`, an unusable `source`/`target` path (empty, empty segment, prototype keys), colliding targets — plus `inputMapping` on `find`/`get`/`delete`, which never read a body (PM ruling: same category as `cacheTtl` on a non-GET); - policy (ADR-0121 D6 + the E4 refusals): `authRequired: false` requires `rateLimit.enabled === true` (presence is NOT armed — `enabled` defaults to `false`), an armed budget must be usable, `cacheTtl` non-negative and GET-only; - uniqueness: one claim per METHOD + normalized path inside a stack. The gate lives on the schema, not in `defineStack`, so every publish/validate seam runs it: `defineStack`, `os validate`, the lint scorer, the metadata plugin's artifact ingestion and `EnvironmentArtifactSchema.metadata`. `normalizeEndpointPath` moves to `@objectstack/spec/api` and the endpoint matcher re-exports it, so the uniqueness gate and the matcher's index key can never disagree about the canonical path form. Upgrade documentation is the security deliverable (maintainer ruling: no activation switch): a `declarative-apis-endpoints-live` semantic migration entry plus a step-17 rationale paragraph instruct the upgrading (AI) maintainer to review every historical `apis:` block before upgrading and to pay particular attention to explicit `authRequired: false`. Both reach `docs/protocol-upgrade-guide.md` and `spec-changes.json` through the ADR-0087 generators. Vocabulary frozen: no key added, removed or renamed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 4, 2026 08:20
os-zhuang
enabled auto-merge
August 4, 2026 08:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5111
Part of #5040(E7 —— 本程序唯一改变行为的一单)
这一单做了什么
#4936 对非空
apis:的整面硬拒在packages/spec/src/stack.zod.ts上是一条.max(0),理由是当时端点面全链路零执行(无挂载、无匹配器、每个键——包括authRequired——解析通过而不生效)。E1–E6 把执行器建成之后,这条理由不复存在,继续拒绝就变成了反方向的谎:一条能跑的能力被挡在门外。本 PR 把它收窄为逐端点门:过门的端点在 publish 之后真实挂载、真实服务流量。门挂在
ObjectStackDefinitionSchema上(不是挂在defineStack里),因此defineStack、os validate、lint 评分、metadata 插件的 artifact 摄入、EnvironmentArtifactSchema.metadata这五条路径没有一条能绕过它。五道门(每道自带处方,点名端点、点名键)
path不是/api/v1/apps/{manifest.namespace}/{subpath};或声明了apis:却没有显式manifest.namespace(Q1 = A,不做deriveNamespaceFromPackageId回落——对外 URL 契约不应因为改了 package id 而漂移)isAppEndpointPath(路由候选判据)type: 'script'/'proxy';object_operation缺objectParams.object或.operation;flow的target为空planEndpointTargettransform;不可用的source/target路径(空串、空段a..b、原型键);互撞的target(同路径 / 一条写进另一条内部);外加 PM 裁决:inputMapping写在find/get/delete上(不读 body,声明必然惰性,与非 GET 的cacheTtl同类同判)mappingDeclarationRejectionauthRequired: false而无已装配限流(判据rateLimit?.enabled === true,不是键存在——enabled的 schema 缺省是false,写了窗口和配额却不写enabled会得到一个「匿名且完全不计量」的端点);已装配但不可用的预算(maxRequests/windowMs≤ 0);负数cacheTtl;非 GET 上的cacheTtlendpointRateLimiterRegistry/cacheControlHeaderendpointIndexKey每一道门的判据都是照读运行时得出的,不是凭记忆复述:接受的集合 = 执行器服务的集合。运行时侧的 501 拒绝保留不动——绕过 publish 直写
metadata.register()的条目仍需要那道兜底。翻转的阳性断言(#4936 之后第一次)
新增测试里第一组就是正面用例:命名空间下的
object_operation端点、flow端点、装配了限流的匿名端点、body 型操作上的映射键——全部通过校验。回归钉子同时保留:空apis:/ 缺省apis:依然合法,没有 namespace 但也没有端点的 stack 依然可发布。升级文档 = 安全承载件(维护者裁决:不加激活开关)
生成机制只从 ADR-0087 registry 取料,所以指令写在 registry 里:
packages/spec/src/migrations/registry.ts新增 semantic 条目declarative-apis-endpoints-live(surface / replacement / reason / acceptanceCriteria),外加 step17 rationale 的一段gen:upgrade-guide/gen:spec-changes落进docs/protocol-upgrade-guide.md与spec-changes.json(本 PR 已重生成)。内容明确指令升级者(通常是 AI 维护者):升级前审视每一处历史
apis:配置;过门的端点在 v17 publish 后即为在线;特别注意显式authRequired: false——schema 缺省是true,漏写是安全的,只有显式false才打开匿名面,且 D6 要求它配一条已装配的限流。未触碰content/docs/releases/。其它
ApiEndpointSchema零改动——门是校验逻辑,不是新键;normalizeEndpointPath上移到@objectstack/spec/api,packages/metadata的匹配器改为再导出。唯一性门与匹配器索引键从此不可能对「规范形式」产生分歧(否则可以发布一对匹配器只会留一条的重复声明);@objectstack/specmajor(与一期 feat(spec,core,runtime)!: 声明式apis:响亮拒绝 + ApiRegistry 整面退役 (#4936, #4939) #5065 同级,同属 v17 破坏面),正文含 FROM → TO 与升级前的安全审视说明。验证(真实输出)
🤖 Generated with Claude Code
https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
Generated by Claude Code