Skip to content

feat(spec,runtime,hono)!: 入站 rateLimit 从零建 seam —— 授权预算真正产生 429 (#4910, #4937) - #5006

Merged
xuyushun441-sys merged 4 commits into
mainfrom
claude/issue-4910-inbound-ratelimit-seam
Aug 3, 2026
Merged

feat(spec,runtime,hono)!: 入站 rateLimit 从零建 seam —— 授权预算真正产生 429 (#4910, #4937)#5006
xuyushun441-sys merged 4 commits into
mainfrom
claude/issue-4910-inbound-ratelimit-seam

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4910
Fixes #4937

父单 #4686(「spec 两份 RateLimitConfig 全仓零 reader」)的入站半边;出站半边已由 #4911/#4947 摘除,两边文件面不相交。按 2026-08-03 维护者裁决(#4910 comment 5169379611)的四问组合执行:Q1=B / Q2=B / Q3=C / Q4=B,不再重议。

前提修正:这不是「接线」,是「从零建 seam」

立单假设 runtime 的 token bucket 已在服务 dispatcher 的 429 短路。上一轮开工核查推翻了它:RateLimiter 零调用点(除自己的单测),全仓入站路径没有任何 429,而 rate-limit.ts 的注释用现在时描述了这条不存在的执行链,连它让你去调的 DispatcherPluginConfig.rateLimit 字段都不存在(#4937)。

所以本 PR 同时关掉两个单:两个半成品各自都不算坏,它们只是从未相连,而两边的文档都写得像已经相连

作者写什么

export default defineStack({
  manifest: { /* … */ },
  server: {
    security: {
      rateLimit: { enabled: true, windowMs: 60_000, maxRequests: 600 },
    },
    trustProxy: false,
  },
});

server:新的顶层 stack 键。此前无人声明得出来(全仓扫描:存量栈与示例应用零命中),所以没有激活语义 —— 不存在「昨天惰性、今天开始吃 429」的配置,因此不需要 ADR-0087 D3 semantic migration,也不需要 upgrade guide 条目。

刻意窄:只承载 security.rateLimittrustProxy,因为只有这两个键有消费者。它不是九键的 HttpServerConfigSchema —— 另外七个既无 reader 又无作者面,顺车挂上来等于一次性把七个死键变成可写(它们的存废仍归 #4938)。从出生即 strict(#4001 战役标准,走 strictObject),拼错的预算带修正被打回,maxRequests: 0defineStack 就被拒。

没有 server.port 监听端口属于部署而非制品,objectstack serve -p 已经拥有它;两个权威争一个数字,配置就退化成建议。优先级规则提前写进 schema describe 与文档,避免日后逐调用点重议:CLI 参数 > server: > 内置默认

发生什么

服务器路由的每一个入站请求(REST、dispatcher、service routes,全部)从令牌桶取token:capacity = maxRequests,refillPerSec = maxRequests / (windowMs / 1000)。桶空 → 429 + Retry-After(由桶自己的 retryAfterMs 算出,所以告诉客户端的等待就是桶真正需要的等待)+ 标准错误信封(code: "RATE_LIMIT_EXCEEDED")。OPTIONS 预检永不计量。

桶按 已解析 principal 计,匿名流量回落到 调用方 IP(Q3=C)。该 IP 仅在 trustProxy: true 显式声明时取转发头,否则取传输层对端地址。未声明时那些头就是攻击者输入:默认相信它,等于给任何人无限量的新桶(绕过),同时允许他冒充受害者把别人的桶顶爆(武器化)。

计数落在 kernel cache(ADR-0069 D2),复用 plugin-auth createLazyCounterStore消费时惰性解析范式,所以晚注册的 cache 插件照样被用上(#4772 的教训)。完全没有 cache 服务时回落到进程内存储,并一次性说清后果与补救:在共享 cache 到位之前,实际限额 = 声明预算 × 节点数,而部署看起来一切正常。

顺带修好的:IHttpServer.use() 原本是个空壳

建这个 seam 时发现,合约声明的中间件层在唯一的真实适配器里是 no-op:两条分支都把 {} as any 当 req/res 传进去,然后无条件 next() —— 中间件读不到请求、写不出响应、拒绝不了继续。没人发现,因为全仓零生产调用点。这就是 declared ≠ enforced 又下一层。

现在它是真的:交付 method/path/query/headers 加传输层对端地址(IHttpRequest.remoteAddress,新增),并且尊重短路。HonoHttpServer 只挂一个 Hono 中间件(链执行器),use() 往它按请求读取的链上追加;HonoServerPlugin.init()末尾放置这个执行器 —— 在 CORS/Server-Timing 之后(429 才带得上 CORS 头,否则浏览器只看到不透明的网络错误),在任何路由之前(路由全在 Phase 2 挂载)。因此 use() 的调用时机不再决定覆盖范围,限流器得以在 start() 安装 —— 那里「本 kernel 没有 http.server」是既成事实而非 Phase 1 中途的猜测,避开 #4771 那类「启动期记录一个 boot 还能推翻的判决」。

明确接线(Q2=B)

ApiEndpointSchema.rateLimitApiEndpointRegistrationSchema.rateLimit 保持 known-unwired,写下去仍然什么也不发生;本 PR 也退役它们。声明式 apis: 整面的存废尚未裁决(#4936),此刻退役一个键,若日后 #4936 选「接上执行」就要把墓碑刨掉重来。已在 DispatcherPluginConfig.rateLimit 的代码注释、schema describe 与文档中显式登记为「已知未接线,由 #4936 跟踪」—— 是响亮的缺席,不是静默。

逐键活性

消费者 断言
security.rateLimit.enabled deriveBucketConfig —— false/缺省时完全不注册中间件
security.rateLimit.maxRequests capacity 有(改它必须改动派生结果)
security.rateLimit.windowMs refillPerSec 有(同上)
trustProxy resolveRateLimitKey 的转发头分支 有(两向都测)

无静默残留键。

测试

先证红后证绿。dispatcher-plugin.rate-limit.integration.test.ts 的第一个 suite 用同样的预算、同样的组合、不接线启动,断言永不出现 429 —— 那就是本 PR 之前的 origin/main。不带这条对照,一句「观察到 429」可以来自任何地方。

  • packages/runtime/src/dispatcher-plugin.rate-limit.integration.test.ts —— 真实 kernel + 真实 socket:RED 对照;429 + Retry-After + 信封;本插件不拥有的路由也被闸住(证明是 server 级而非 dispatcher 级);更早启动的插件挂的路由也被闸住(@objectstack/rest 的形状);principal 各自计桶;预检不计量;trustProxy 两向分支。
  • packages/runtime/src/security/inbound-rate-limit.test.ts —— 派生、key 形状、共享计数(两节点同一预算)、降级日志只出一次且带后果与补救、惰性解析。
  • packages/plugins/plugin-hono-server/src/middleware-seam.test.ts —— 短路 / 透传 / 读请求三项能力,seam 位置的正反两面都钉住。
  • packages/spec/src/system/stack-server.test.ts —— 作者面:穿过 defineStack、只有两个键、七个未消费键各自带处方被拒、strict、零预算被拒。
packages/spec              Test Files 298 passed   Tests 7524 passed
packages/runtime           Test Files  82 passed   Tests 1130 passed
packages/plugins/plugin-hono-server  Test Files 13 passed  Tests 150 passed
packages/plugins/plugin-auth         Test Files 29 passed  Tests 658 passed
pnpm typecheck             Tasks: 122 successful, 122 total
spec check:generated       ✓ All 8 generated artifacts are up to date
example validate (crm / showcase / todo)  EXIT=0

另跑绿:check:livenesscheck:strictness-ledgercheck:exported-anycheck:dual-source-exportscheck:empty-state,以及仓级 check:startup-registry-verdictcheck:durability-log-levelcheck:init-service-contractcheck:route-envelopecheck:error-code-casingcheck:service-providerscheck:wildcard-fallthroughcheck:doc-authoringcheck:adr-anchors 等 22 项。

其他

  • changeset:.changeset/inbound-rate-limit-seam.md(spec/runtime/hono minor,auth/cli patch)。.changeset/pre.json 开工时已确认仍是 mode:pre / tag:rc。
  • 文档:content/docs/protocol/kernel/http-protocol.mdx 的 Rate Limiting 一节原本描述 X-RateLimit-* 头与 THROTTLED 信封并注明是「intended contract」—— 那些我们发。改写为真实行为,并明确点名未实现的部分。⛔ 未碰 content/docs/releases/
  • 已 os-regen 四步(merge origin/main → checkout origin/main 生成物 → 整体重生成 → 断言兄弟条目存活):spec-changes.json / protocol-upgrade-guide.md 与 main 逐字节一致(批 11/12 条目完好),我方 delta 恰为 6 个 authorable key + 7 个导出,0 removed
  • 范围外发现已立单 composeStacks silently drops every non-array top-level key — api: today, server: as of #4910 #5005(未认领):composeStacks 静默丢弃一切非数组顶层键 —— api:(含 enforceProjectMembership 这个 403 闸门)今天就在丢,server: 从本 PR 起同样,合成语义需维护者裁一次。

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9


Generated by Claude Code

claude added 4 commits August 3, 2026 19:20
… 429 (#4910, #4937)

`packages/spec` declared three `RateLimitConfig` embeddings with zero readers
repo-wide, and `runtime/security/rate-limit.ts` held a token bucket with zero
call sites whose comments described, in the present tense, an execution chain
that did not exist. Neither half was broken; they were never connected, and both
were documented as if they were.

This builds the seam the 2026-08-03 adjudication specified:

- new NARROW `server:` stack key (`security.rateLimit` + `trustProxy` only —
  the other seven `HttpServerConfigSchema` keys stay unreachable, #4938), strict
  from birth, rejecting an unusable budget at `defineStack`;
- `createDispatcherPlugin({ rateLimit })` builds the limiter and installs it as
  global middleware in `init()`, so it gates every route the server mounts;
- keyed by resolved principal, falling back to caller IP; forwarded headers
  honoured only under an explicit `trustProxy`;
- counters in the kernel cache (ADR-0069 D2) via plugin-auth's lazy resolution,
  with an announced per-process fallback naming the consequence;
- `IHttpServer.use()` made a real middleware seam — the Hono adapter passed `{}`
  for req/res and always called `next()`, so no middleware could ever act.

Endpoint-level `rateLimit` stays knowingly unwired (#4936), registered as such.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…iter can install in start() (#4910)

Registering the rate-limit middleware in the dispatcher's `init()` meant reading
the `http.server` registry while Phase 1 was still filling, drawing a terminal
"no transport" conclusion from it, and recording that conclusion in a warn — the
exact three-part shape `check:startup-registry-verdict` exists to stop (#4771).

Cured structurally rather than tolerated. `HonoHttpServer` now mounts its chain
runner via `installMiddlewareSeam()`, which `HonoServerPlugin.init()` calls at
the very end — after CORS/Server-Timing (so a 429 still carries CORS headers)
and before any route exists. `use()` appends to a chain that runner reads per
request, so registration order stops deciding coverage, and the dispatcher can
install the limiter in `start()` where "no http.server" is a settled fact.

Covered both ways: a route mounted by an earlier plugin's start() is still
gated (integration), and the adapter pins the negative case too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
… four-step)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
@vercel

vercel Bot commented Aug 3, 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 3, 2026 8:07pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests tooling labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/cli, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/runtime, @objectstack/spec.

119 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 packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, packages/runtime, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @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/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • 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/cli, @objectstack/runtime, 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 @objectstack/runtime, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/runtime, @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @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/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli, @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • 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/cli, @objectstack/plugin-hono-server, @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime, @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/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, 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/plugin-auth, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime, @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/permissions/sso.mdx (via @objectstack/plugin-auth)
  • 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/cli, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/runtime, @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/plugin-hono-server, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • 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/cli, @objectstack/plugin-auth, @objectstack/plugin-hono-server, @objectstack/runtime, @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/cli, @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/cli, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth, @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.

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 20:10
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 55dbbba Aug 3, 2026
25 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4910-inbound-ratelimit-seam branch August 3, 2026 20:30
xuyushun441-sys pushed a commit that referenced this pull request Aug 4, 2026
Brings in #5003 (批 13), #5006, #4983, #4991, #4855, #4729, #4250.

Ledger `ui/` section conflicted, as planned — 批 13 and 批 14 both edit it. Every
row from both sides kept; nothing resolved in favour of a side:

  - classification table: ONE merged `no door` row (批 13 authored the class;
    批 14 adds the positive-control requirement and the first file that SPLITS
    across it, ui/sharing.zod.ts).
  - triage table: structure from 批 13 (it split `responsive` out and grouped the
    five no-door files); 批 14's four measured verdicts overlaid.
  - remaining-strip map: `responsive` row deleted by 批 13, `action`/`report`/
    `dataset`/`dashboard` rows deleted by 批 14 — all five by the reverse pin.

Header and subtotal RECOMPUTED FROM THE SURVIVING ROWS, not decremented:
29+20+14+9+7+7+6+4+4+4+3+1+1+1 = 110. 批 13 wrote 119 (against a tree where 批 14's
four rows still existed), 批 14 wrote 114 (against one where `responsive` still
did); both were right against their own branch and both are wrong against the
merge. The fifth and sixth instances of the failure the automation/ section
documents, and the first where the two wrong numbers were both this line. Subtotal
88 of 110 authorable, 22 in the fourth class. check:strictness-ledger arbitrates
and is green.

os-regen four-step: merged (never rebased), refreshed install + spec build, reset
all seven generator-owned paths to origin/main, regenerated wholesale. Delta vs
origin/main is exactly one file — content/docs/references/ui/sharing.mdx — so the
regeneration reproduced main's artifacts byte-identically and no sibling entry was
dropped (#5006's RateLimitConfig/RateLimitConfigSchema and 批 13's responsive
entries confirmed present).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 4, 2026
…stack-ai#4936, objectstack-ai#4939) (objectstack-ai#5065)

* feat(spec,core,runtime)!: reject non-empty `apis:` loudly; retire the ApiRegistry family (objectstack-ai#4936, objectstack-ai#4939)

The declarative `apis:` surface was zero-execution end to end while reporting
perfect health. Metadata loaded fine — `GET /api/v1/meta/api` returned every
declared endpoint with every key — but no route was ever mounted for a declared
path, so a request died at Hono's `notFound` (a bare 404, not the dispatcher's
semantic one), and the `handleApiEndpoint` branch behind it called a
`matchEndpoint` method no implementation in this repo has ever provided. Every
key on `ApiEndpointSchema` was therefore declared != enforced, `authRequired`
included — a security semantic that parsed green and gated nothing.

Per the maintainer verdict (2026-08-04, objectstack-ai#4936), this takes the third route:
keep the vocabulary, refuse the authoring.

- spec: a non-empty `apis:` is rejected on `ObjectStackDefinitionSchema` — the
  one choke point `defineStack`, metadata artifact ingestion, `os validate`,
  the lint scorer and `EnvironmentArtifactSchema` all run through, so no path
  can forget to check. The rejection carries its own prescription and names
  objectstack-ai#5040 (the executor) as the live tracker. Empty/absent still pass.
  `ApiEndpointSchema` itself is untouched: retiring an industry-stable endpoint
  shape would only mean re-introducing it identically later.
- runtime: `handleApiEndpoint`, its now-orphaned private `callData` delegate
  (tsc TS6133 found it) and the `/__api-endpoint` ledger + legacy-prefix
  entries are deleted, so the absence is loud instead of grep-able dead code.
- spec/core (objectstack-ai#4939): the second, unrelated endpoint declaration shape retires
  whole — `ApiEndpointRegistration`/`ApiRegistry`/`ApiRegistryEntry` and their
  value schemas (12 JSON-Schema defs, 67 authorable keys), the ~500-line
  `ApiRegistry` service, `createApiRegistryPlugin`, and hono's unread
  `useApiRegistry` option. It was composed only in `packages/core/examples/`,
  so `requiredPermissions` promised gateway enforcement no gateway performed.
  `ConflictResolutionStrategy` survives, moved to `api/router.zod` — two
  independent ratchets (spec sync-retirement, objectui parity) pin it.
- showcase: declares no endpoints (both definitions preserved, commented, with
  the rationale); the coverage manifest's "demonstrated ... executed by the
  runtime dispatcher" claim is corrected to a waiver — it was the exact
  advertise-what-you-do-not-deliver claim Prime Directive objectstack-ai#10 forbids.
- the objectstack-ai#4910/objectstack-ai#5006 endpoint-level `rateLimit` tracking pointers now name objectstack-ai#5040,
  since objectstack-ai#4936 closes here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

* chore(changeset): runtime bump is major — `handleApiEndpoint` was a public method

`HttpDispatcher.handleApiEndpoint()` carried no `private` modifier, so deleting
it removes a symbol from `@objectstack/runtime`'s public surface even though the
method returned `{ handled: false }` on every call it ever received. Record it
as breaking with that nuance stated, rather than letting a `minor` imply the
symbol survived. Also drops the `@objectstack/client` entry: that change is a
comment in a `.test.ts`, which never ships.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

* style(spec): keep the schema's own doc comments adjacent to the schema

The APIS_NO_EXECUTOR_GUIDANCE const landed between the two doc-comment
blocks that both belong to ObjectStackDefinitionSchema, orphaning the
first. Move the const above them — no behaviour change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

* test(spec): make the tracking-pointer pin assert what its name promises

The test claimed the prescription does not name objectstack-ai#4936 as the tracker but
only asserted objectstack-ai#5040 was present — true even with a stale issues/4936 link
beside it. Assert the set of issue URLs in the message is exactly {5040}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

* docs(spec,showcase): the prescription must not over-promise — ADR-0121 D1 renames paths on restore

ADR-0121 (accepted 2026-08-04, after this branch opened) namespaces
endpoint paths as `<runtime-prefix>/apps/<namespace>/<subpath>`. The
rejection message said definitions "stay valid"; that is true of every
key except `path`, so it is now stated precisely, with the FROM -> TO.
The showcase's commented endpoints carry the same note — they would be
rejected under D1 if uncommented verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 4, 2026
…stack-ai#5040 E4) (objectstack-ai#5135)

* feat(runtime): endpoint policy keys — authRequired / rateLimit / cacheTtl (objectstack-ai#5091)

Wire the three policy keys `ApiEndpointSchema` declares, in the order objectstack-ai#5040 §3
fixes (rateLimit → authRequired → cacheTtl), reusing existing primitives only:

- `authRequired` → `shouldDenyAnonymous` + the `ANONYMOUS_DENY_*` constants, so
  a declared endpoint answers the same 401 as `/meta`, `/ai` and `/security`.
  The key arrives materialized (schema default `true`), so there is no
  "omitted" state a consumer could read differently.
- `rateLimit` → objectstack-ai#5006's `deriveBucketConfig` / `resolveRateLimitKey` /
  `SharedTokenBucketLimiter` over the shared counter store, keyed
  `apiep:<name>:<principal|ip>` so the endpoint budget and the server-level
  budget are independent rather than one budget counted twice. Over limit
  answers the server limiter's own 429 body plus `Retry-After`.
- `cacheTtl` → response-header semantics only (objectstack-ai#5091 ruled out a server-side
  cache): `private, max-age=<ttl>` for a positive ttl, `no-store` for 0 or
  negative, nothing when absent, nothing + a warn on a non-GET endpoint.

Metering runs BEFORE the auth gate on purpose: credential stuffing is anonymous
traffic against an `authRequired` endpoint, and gating first would let a scanner
make unlimited attempts none of which ever reach the meter.

The dispatch step runs the chain between the match and its 501, and target
execution can only land on the far side of the chain — the branch is
unreachable without a policy context, so wiring an executor without wiring
policies is not something a later change can do by forgetting.

Structurally unreachable and zero live behavior change: a non-empty `apis:` is
still rejected at publish until the objectstack-ai#5040 E7 flip, and the step's answer without
a policy context is byte-identical to today's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

* fix(runtime): strip two NUL bytes from endpoint-policy.ts cacheKey literal (objectstack-ai#5091)

分隔符误写为 \x00:git 判文件为二进制,ESLint / check:nul-bytes 红。
替换为空格,registry 内部键行为等价。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <support@objectstack.ai>
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 protocol:system size/xl tests tooling

Projects

None yet

2 participants