Skip to content

test(e2e,showcase): 执行器收官验收 —— showcase 端点回迁 + 真实 boot 探针(#5040 E8) - #5230

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-5112-acceptance
Aug 4, 2026
Merged

test(e2e,showcase): 执行器收官验收 —— showcase 端点回迁 + 真实 boot 探针(#5040 E8)#5230
os-zhuang merged 3 commits into
mainfrom
claude/issue-5112-acceptance

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5112
Part of #5040(E8 —— 收尾件)

这一单做了什么

#4936 把 showcase 的两条 apis: 声明注释掉,理由是当时端点面全链路零执行:没有挂载、没有匹配器、每个键(包括 authRequired)解析通过而不生效。E1–E7 建成执行器、把整面硬拒收窄为逐端点 publish 门之后,那条理由不复存在。本单按原意恢复这两条 —— 同名、同 target、同 authRequired、同 cacheTtl —— 只做 ADR-0121 D1 要求的一处修改:

- '/api/v1/showcase/tasks'            + '/api/v1/apps/showcase/tasks'
- '/api/v1/showcase/inquiries/purge'  + '/api/v1/apps/showcase/inquiries/purge'

匿名面没有增加:两条历史声明本来就都是 authRequired: true。一个例子不该长出它从来没有过的公开面 —— 匿名分支改由一份一次性 fixture 承担(见下)。

RED-first(证据,不是说法)

两份 e2e 的断言先于 showcase 回迁写就并运行。pre-restore 状态下:

 FAIL  ... > GET /meta/api returns both restored declarations
 FAIL  ... > object_operation endpoint > answers 200 for an authenticated caller
 FAIL  ... > object_operation endpoint > carries the declared cacheTtl as a Cache-Control header
 FAIL  ... > object_operation endpoint > DENIES an anonymous caller with 401 — authRequired finally gates
 FAIL  ... > object_operation endpoint > never lets an error answer carry a cache directive
 FAIL  ... > flow endpoint > answers 200 for an authenticated caller and runs the janitor flow
 FAIL  ... > flow endpoint > DENIES an anonymous caller with 401
 FAIL  ... > /openapi.json > documents the object_operation endpoint under its declared path
 FAIL  ... > /openapi.json > documents the flow endpoint under its declared path
 Test Files  1 failed (1)
      Tests  9 failed | 5 passed (14)

九条全部是 #4936 记录的那个裸 404。通过的 5 条正是两个状态下都必须成立的:挂载点下未声明路径的裸 404、挂载点外的裸 404、方法不匹配、以及「不宣告运行时不服务的东西」。回迁后:

 Test Files  2 passed (2)
      Tests  22 passed (22)

两份 e2e,分工是刻意的

showcase-declarative-endpoints.dogfood.test.ts —— 走真实 artifact 摄入路径(MetadataPlugin + local-file,createStandaloneStack 用的同一条),所以这次 boot 本身就再证明了一遍 E7 的 publish 门接受回迁后的声明。覆盖:匹配命中执行、finddata 与内建 /data 路由逐字节相同、匿名 401、cacheTtl 只随成功答案上线、挂载点下未声明路径与挂载点外裸 404 byte-identical/meta/api/openapi.json 描述的正是挂载的东西。

#5112 的验收注记,断言 components.schemas 与内建 $ref 解析(存量 #5168),只断言端点 path 条目。

declarative-endpoint-policy.dogfood.test.ts + fixture —— ADR-0121 D6 的匿名分支。省略 authRequired 拒绝匿名(升级指南「omission is SAFE」这句话第一次被测量而不是被相信)、显式 false 服务匿名、已装配预算耗尽后 429 且 Retry-After 真的在线上、429 不带缓存指令、预算是逐端点的。外加 E7b(#5189/#5203)的直写拒绝探针:绕过 publish 直接往 store 写一条 authRequired: false 且无已装配预算的条目,两种调用者都拿 404。

真实 boot 探针(原始输出,已裁剪)

pnpm dev:showcase -- --fresh -p 39720 --seed-admin,47 plugins,29 flows,130 seed rows。

########## P1  GET /api/v1/apps/showcase/tasks  (authed)
HTTP/1.1 200 OK
cache-control: private, max-age=30
content-type: application/json
  body keys: ['success', 'data']    data keys: ['object', 'records', 'total', 'hasMore']
  total: 10  records: 10   first record title: Audit current IA

########## P2  GET /api/v1/apps/showcase/tasks  (ANONYMOUS)
HTTP/1.1 401 Unauthorized
  {"success":false,"error":{"code":"UNAUTHENTICATED","message":"Authentication is required to access this endpoint.","httpStatus":401}}

########## P3  POST /api/v1/apps/showcase/inquiries/purge  (authed)
HTTP/1.1 200 OK
  {"success":true,"data":{"success":false,"error":"Node 'purge' failed: delete_record(showcase_inquiry) failed:
   Delete requires an ID or options.multi=true","durationMs":9,"summary":{"selected":1,"acted":0,...}}}
   ↑ 流本身的存量缺陷,已立 #5225;对照组见下

########## P4  POST purge (ANONYMOUS)                     HTTP/1.1 401 Unauthorized
########## P5  GET /api/v1/apps/showcase/no-such-endpoint  HTTP/1.1 404  {"error":"Not found"}
########## P6  GET /api/v1/no-such-route-e8                HTTP/1.1 404  {"error":"Not found"}
          byte-identical to P5: YES
########## P7  POST /api/v1/apps/showcase/tasks (方法不匹配)
HTTP/1.1 404 Not Found   {"error":"Not found"}   byte-identical to bare 404: YES

########## CONTROL: 同一条流走内建 automation 路由
POST /api/v1/automation/showcase_inquiry_purge/trigger → HTTP/1.1 200 OK
  success: False   error: Node 'purge' failed: delete_record(...) Delete requires an ID or options.multi=true
  ↑ 与 P3 逐字相同 —— 执行器是忠实的,坏的是流

########## P8  GET /api/v1/meta/api
  - showcase_task_feed        GET  /api/v1/apps/showcase/tasks            authRequired=True  cacheTtl=30
  - showcase_inquiry_purge_api POST /api/v1/apps/showcase/inquiries/purge authRequired=True  cacheTtl=None

########## P9  GET /api/v1/openapi.json(仅端点 path 条目;#5168 的 schemas 不断言)
   GET  /api/v1/apps/showcase/tasks
     operationId: showcase_task_feed | summary: Task feed
     security: [{"bearerAuth": []}] | responses: ['200', '401']
   POST /api/v1/apps/showcase/inquiries/purge
     operationId: showcase_inquiry_purge_api | summary: Purge closed inquiries
     security: [{"bearerAuth": []}] | responses: ['200', '401']
  total paths in doc: 201

没有 429 的真实 boot 探针,因为回迁的两条都没有声明 rateLimit —— 而给一个已发布示例加一条它历史上没有的限流(必然连带一条匿名端点)正是不该做的事。429 + Retry-After 由 fixture 那份 e2e 在同样真实的 boot 上证明。

P7 的说明:声明式端点走的是 setFallbackHandler(Hono notFound),不是注册路由,所以方法不匹配是 404 而不是 405 + Allow —— 与「挂载点不改变任何人的未匹配答案」是同一件事。405 + Allow 的对照在已注册路由上仍然成立。

升级文档安全复核 —— PASS

docs/protocol-upgrade-guide.mddeclarative-apis-endpoints-live 条目,逐条对着实现读:

文档的说法 实现 判定
path 移到 /api/v1/apps/{manifest.namespace}/{subpath} namespaceGate + DEFAULT_RUNTIME_PREFIX + APP_ENDPOINT_SEGMENT
namespace 必须显式,无 manifest.id 推导回落 NAMESPACE_RE,无 derive 分支(Q1=A)
authRequired 缺省 true,省略是安全的 e2e 实测:省略键的端点对匿名返回 401 ✅ 已测量
只有显式 false 打开匿名 e2e 实测:显式 false 的端点匿名 200 ✅ 已测量
D6 判据是 rateLimit.enabled === true,不是键存在 policyGate:authRequired === false && !armed
「写了窗口和配额却不写 enabled 会得到匿名且完全不计量的端点」 实测反证:临时插入该形状,objectstack validate 判红并给出处方 ✅ 见下
验收 (3):objectstack validate 证明没有 17.x 不能执行的形状 五道门全部在场(target / mapping / policy / uniqueness / namespace)
验收 (4):匿名请求 session-only 端点返回 401 而不是数据 P2 / P4

反证的原始输出:

✗ defineStack validation failed (1 issue):
✗ apis.2.rateLimit: Endpoint 'e8_doc_review_probe' (apis[2]) declares `authRequired: false`
  without an ARMED rate limit. ... declare `rateLimit: { enabled: true, windowMs: 60000,
  maxRequests: 100 }`. `enabled` is not `true` — and it DEFAULTS to `false`, so writing only
  `windowMs` / `maxRequests` declares a budget that meters nothing and the endpoint would be
  anonymous AND unmetered. ...

指令在场、准确、可执行 —— 一个没有其它上下文的 AI 维护者照着 grep authRequired: false、照着报错处方改,就能落地。未编辑生成文件。

两条精度备注(不构成 FAIL,建议走 registry 修):

  1. 条目把 /api/v1 写成字面量,而门确实硬编码 DEFAULT_RUNTIME_PREFIX —— 重设了 dispatcher prefix 的部署无法发布任何端点。这是 17.x 立项:建设声明式 ApiEndpoint 执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线) #5040 §7-8 的已知开放问题,文档里没有点出这个前提。
  2. grep 指引只覆盖源码里的 apis: 块。已经以 sys_metadata 行存在的 api 条目(pre-17 的 Studio 写入)没有东西可 grep —— 而那正是 声明式端点的两个机器可读面会说谎:runtime-authored api 行在 /meta/api 与 /openapi.json 里在场,匹配器却永远看不见(真实 boot 实测) #5224 那条路径。

如实报告的三件事

多租户交错验收(E5b)—— 无法表达,不是没做。 端点步骤的多租户分支判据是 isMultiTenantHost(),即 kernel-resolver 服务是否注册。全仓 grep:该服务在本仓只有消费者没有提供者(http-dispatcher.tsrest-api-plugin.tscurrent-user-endpoints.ts 都是 getService),提供方在 cloud 分发里。bootStack({ multiTenant: true }) 给的是多组织(tenancy posture),单内核,isMultiTenantHost() 恒为 false。所以「按请求自己的环境解析」与「环境解析不出 → 拒绝服务(404 + warn)」两条在本仓的任何真实 boot 上都不可达。顺带查出:那条 decline 分支的提示语在全仓只出现在 dispatcher-plugin.ts 源码里,没有任何测试引用它 —— 该分支目前零覆盖。 建议作为 E 系列的收尾 followup 单独派发(需要在 packages/runtime 里用桩 kernelResolver 驱动)。

composed-stack(设计 §7-7)—— 仍然开放。 composeStacks 在本仓只有 packages/spec 自己的定义与测试引用,examples/ 与任何 first-party 启动路径都不组合 stack。所以「组合栈的端点加载」在示例层无法探针,E7 的结论(composeStacks 不重新解析)未被本单改变。

覆盖真相。 E 系列没有留下 istanbul/c8 豁免或 coverage 配置条目;它留下的是散在源码里的「结构性不可达」断言,而这些断言在 E7 翻转 + 本单回迁之后已经不成立了。这些文件都在本单文件面之外(#4604),仅列出供 PM 派发:

顺手发现的、已单独立项的

验证(真实输出)

example-showcase verify   validate ✓(仅存量 warning)· tsc --noEmit clean · 11 files / 87 tests passed
dogfood 新增两份 e2e      Test Files 2 passed (2) / Tests 22 passed (22)
dogfood 全量             Test Files 85 passed | 1 skipped (86) / Tests 505 passed | 3 skipped (508)
dogfood typecheck        tsc --noEmit clean
pnpm build               Tasks 71 successful, 71 total(artifact 内 apis 两条,路径已在 carve-out 内)
eslint                   clean(六个改动文件)

现场已清:dev server 全部按 PID 停止,--fresh 的 OS_HOME 临时目录随退出自动删除,e2e 自己的 tmpdir 在 afterAllrmSync


🤖 Generated with Claude Code

https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd


Generated by Claude Code

claude added 3 commits August 4, 2026 10:47
#4936 把 showcase 的两条 `apis:` 声明注释掉,理由是当时端点面全链路零执行:没有
挂载、没有匹配器、每个键(包括 `authRequired`)解析通过而不生效。E1–E7 建成执行器
并把整面硬拒收窄为逐端点 publish 门之后,那条理由不复存在。本单把两条声明按原意
恢复 —— 同名、同 target、同 `authRequired`、同 `cacheTtl` —— 只做 ADR-0121 D1
要求的一处修改:路径迁入本应用的命名空间保留区 `/api/v1/apps/showcase/…`。

- examples/app-showcase:恢复 TaskFeedEndpoint / InquiryPurgeEndpoint;coverage
  的 `apis` 从 waived 翻回 demonstrated(理由改写为「由真实 boot 测量」而不是
  「声明即证明」—— 这正是 #4936 抓到的那类假覆盖);gap-fill 的反向断言改回正向,
  并新增命名空间与「零匿名面」两条静态钉子。
- packages/qa/dogfood:两个真实 boot e2e。showcase 一份走真实 artifact 摄入路径
  (MetadataPlugin + local-file),证明匹配命中执行、find 的 data 与内建 /data
  路由逐字节相同、匿名 401、cacheTtl 只随成功答案、挂载点下未声明路径与挂载点外
  的裸 404 完全一致、/meta/api 与 /openapi.json 描述的正是挂载的东西;fixture
  一份补上 ADR-0121 D6 的匿名分支 —— 省略 authRequired 拒绝匿名、显式 false 服务
  匿名、已装配预算耗尽后 429 且 Retry-After 真的在线上。

RED-first:两份断言先于 showcase 回迁写就并运行,pre-restore 状态下 9 条失败(全部
是 #4936 记录的那个裸 404),回迁后 14 + 7 全绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
E7b(#5189/#5203)在 main 上落地之后,「一次绕过 publish 的直写不会变成活路由」
从待办 TODO 变成可以真实断言的事:往 store 里直接写一条 `authRequired: false`
且没有已装配预算的 api 条目(D6 最危险的那个形状),两种调用者都必须拿到 404 ——
不是「因为匿名所以 401」,而是压根没有这条路由。

同时把 fixture 里那条「故意省略 authRequired」的声明改标 `ApiEndpointInput`:
`ApiEndpoint` 是 schema 的输出类型,`.default(true)` 已被物化,该键在上面是必填,
所以用它做注解的 TS 作者根本写不出升级指南推荐的那个安全形状(另立 #5227 记录)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
@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 11:10am

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file 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/dogfood.

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

  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)

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

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E8(#5040 执行器):验收 —— showcase 端点回迁 /apps/showcase/… + RED-first e2e + 真实 boot 探针 + 升级文档安全复核

2 participants