Skip to content

feat(spec): 执行器契约面 matchEndpoint? + setFallbackHandler?(#5040 E1) - #5097

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5080-executor-contracts
Aug 4, 2026
Merged

feat(spec): 执行器契约面 matchEndpoint? + setFallbackHandler?(#5040 E1)#5097
os-zhuang merged 1 commit into
mainfrom
claude/issue-5080-executor-contracts

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5080
Part of #5040(执行器 E 系列第 1 单,contract-first 首件)

这个 PR 做什么

纯声明,零行为变更。 只在 packages/spec/src/contracts/ 增加两个可选契约成员与一个导出类型。仓内没有任何实现体、没有任何接线;声明式 ApiEndpoint 在 v17 仍被 publish 硬拒(#4936 裁决),本单落的只是它未来得以执行所需的契约前件。E2/E3 的实现单 Blocked-by: 本单。

#5080范围收窄评论,IMetadataService.generateOpenApi? 已从范围中剔除(#5078 实测:GET /openapi.jsonpackages/rest 独占应答,在 metadata 契约上加该成员会造出 ADR-0076 禁止的第二属主),本 PR 未实现它。#5080 正文的 Blocked-by: v17 切版 已由维护者放行(见认领评论)。

两个成员

1. IMetadataService.matchEndpoint?(query: { path: string; method: string })

把一次请求的 method+path 解析为拥有该路由的 api 元数据条目,未命中返回 undefined。这正是 HTTP dispatcher 在「内建 domain 均未认领」与「答语义 404」之间空出的那一步。随之导出新类型 ApiEndpointMatch:

  • endpointApiEndpointSchema.parse 之后的形状 —— 默认值已物化,而非存储里的原始 JSON。作者漏写 authRequired 时消费端拿到的是 true(schema 默认值),因此消费端永远读不到「缺省」这个中间态,也就不可能把一个缺失的安全默认误读成放行。这是「防 AI 写元数据犯错」轴上的主要设计点。
  • params 在 17.x 恒为 {} ApiEndpointSchema.path 词表已冻结(ADR-0121),既未定义 :param 也未定义 {param},本契约刻意不发明模板语法 —— 只存在于实现里的语法就是隐藏方言(Prime Directive Add comprehensive test suite for Zod schema validation #12)。槽位现在就声明出来,是为了将来真要加路径模板时,那是词表的加法,而不是本契约的破坏性变更。

匹配维度(#5040 设计 §2)一并写进 doc-comment:method 大小写不敏感;path 去尾斜杠后整串精确比较,17.x 不做百分号解码、不做 Unicode 规整、不做大小写折叠。另注明作用域即实例(无 env 参数,与仓内其余 metadata 消费同构),以及「未命中 ≠ 故障」——读不到存储必须 throw,不得把故障伪装成 404(与 loadDiagnosed 同一区分)。

2. IHttpServer.setFallbackHandler?(handler: RouteHandler)

设计 §1 方案 C 的传输层兜底 seam。doc-comment 载入两条语义保证:

  1. 仅在全部显式注册的路由均未命中后调用。 它在结构上不可能遮蔽任何已注册路由,因此零注册顺序依赖 —— 这正是它优于备选「通配路由」方案的原因:后者的归属由插件 start() 顺序下的 first-registration-wins 决定,即 ADR-0076 D11「一条路由一个属主」要防的病灶。实现方映射到框架自身的 not-found 钩子(Hono 的 app.notFound),而非映射到一条路由。
  2. handler 收到的 req.body 可读,与 use() 中间件契约明确的「body 不填充」相反(在 use() 处解析 body 会在真正拥有它的路由 handler 之前吃掉请求流,见 packages/plugins/plugin-hono-server/src/adapter.ts:362)。这条差异正是中间件 seam 无法承载动态端点、必须新增本成员的原因:由 flow 或 create 操作支撑的声明式端点必须读 body。

另注明「重复调用是替换而非追加(只有一个兜底器,不是链)」,以及「handler 不写响应时,适配器既有的 404/405 未匹配语义保持不变」。

可选性

两者均为可选成员,消费端按仓内既有惯例以 typeof x === 'function' 探测(同 watch? / subscribe? / getRawApp?)。不实现它的 metadata 槽位占用者、无法表达 not-found 钩子的适配器,都仍然满足契约。对现有实现方无迁移动作。

契约测试

与既有 contracts 测试同风格同位置(packages/spec/src/contracts/*.test.ts):可选成员的在场/缺席探测(typeof === 'function')、以带类型字面量做的类型层形状断言。setFallbackHandler 侧另有两条行为性断言 —— 已注册路由不被兜底器遮蔽、兜底 handler 读得到 body;matchEndpoint 侧真正走一遍 ApiEndpointSchema.parse,断言作者漏写的 authRequired 在返回值里已物化为 true

生成物 regen 范围

按 AGENTS.md 的 os-regen 纪律执行(buildcheck:generatedcheck:generated --fix,只重生成被证明陈旧的那一件,不整套刷)。

api-surface.json 新增一行 ApiEndpointMatch (interface),0 breaking / 1 added。两个新成员是 interface 成员而非导出,不动其余七件生成物 —— check:generated 复跑 8/8 全绿。无本次改动之外的漂移。

验证结果(实跑)

结果
pnpm --filter @objectstack/spec test Test Files 302 passed (302) / Tests 7663 passed (7663)
pnpm --filter @objectstack/spec typecheck tsc --noEmit 无输出
pnpm --filter @objectstack/spec check:generated All 8 generated artifacts are up to date.
check:exported-any no exported type resolves to any: 1843 types + 1594 schemas across 16 entry points
check:dual-source-exports no new dual-source exports: 4243 names across 16 entry points
turbo typecheck(metadata / runtime / plugin-hono-server / rest 及其依赖) 29 successful, 29 total
eslint 四个改动文件 ✅ exit 0

补充:packages/spec/tsconfig.json 既有地 exclude**/*.test.ts(仓内记录在案的 TEST_DEBT),所以 typecheck 门读不到测试文件。为确认新增的类型层断言真的编译得过,另用一份临时 tsconfig 单独对两个测试文件跑了 tsc:新增代码零报错,仅剩 4 条改动前就存在的 TS6133 未用形参(status: function (code) 等)。临时 tsconfig 已删除。

未做的事

  • 无 runtime / hono 侧实现或接线(E2/E3,另单);
  • 未触碰 ApiEndpointSchema(词表冻结,ADR-0121);
  • 未触碰 content/docs/releases/(Prime Directive —— 发版说明由 changeset 集中编译)。

Changeset:.changeset/executor-contract-surface-e1.md(@objectstack/spec minor)。


🤖 Generated with Claude Code

https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd


Generated by Claude Code

…andler? (#5080)

Part of #5040 (E1, contract-first). Pure declaration: two OPTIONAL contract
members plus one exported type. No implementation, no wiring, zero behavior
change — declared `apis:` are still hard-rejected at publish in v17 (#4936).

IMetadataService.matchEndpoint?(query: { path, method })
  Resolves a request's method+path to the declared `api` item that owns it,
  or undefined on a miss — the dispatcher step between "no built-in domain
  claimed this" and "answer a semantic 404". New exported type
  ApiEndpointMatch:
    - `endpoint` is the ApiEndpointSchema.parse-d shape, schema defaults
      MATERIALIZED, so a consumer can never read a missing `authRequired`
      as permissive.
    - `params` is always {} in 17.x. The frozen ApiEndpointSchema vocabulary
      (ADR-0121) defines no template syntax and this contract deliberately
      does not invent one — a syntax living only inside an implementation is
      the hidden dialect Prime Directive #12 forbids. The slot is declared
      now so path templates would be an additive vocabulary change rather
      than a breaking contract change.

IHttpServer.setFallbackHandler?(handler: RouteHandler)
  The last-resort handler, invoked only after every explicitly registered
  route has missed. Structurally incapable of shadowing a registered route,
  hence zero registration-order dependency — unlike the wildcard-route
  alternative, whose ownership is decided by first-registration-wins across
  plugin start() order (the ADR-0076 D11 hazard). Second guarantee, also in
  the contract: the handler's `req.body` IS readable, in contrast with the
  use() middleware contract which explicitly does not populate it. That
  difference is why the middleware seam cannot carry dynamic endpoints.

Both members are optional and feature-detected with typeof === 'function',
matching watch? / subscribe? / getRawApp?. No migration for implementors.

Contract tests mirror the existing contracts-test style: optional-member
presence/absence probing, and type-level shape assertions via typed literals.

Generated: api-surface.json gains exactly one line, ApiEndpointMatch
(interface) — 0 breaking, 1 added. The two members are interface members,
not exports, so the other seven artifacts are untouched.
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 4:14am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E1(#5040 执行器契约面):IMetadataService.matchEndpoint? / generateOpenApi? + IHttpServer.setFallbackHandler? 可选契约方法与契约测试

2 participants