fix(core,plugin-dev,runtime): every in-memory fallback and dev stub self-describes honestly (#4058 step 1) - #4082
Merged
Conversation
…elf-describes honestly (#4058 step 1) ADR-0076 D12 gave services one standard way to say "I am not the real thing" (`__serviceInfo`), but the producers never converged on it, and the result was wrong in both directions: - The kernel's OWN fallbacks (createMemoryCache/Queue/Job/I18n/Metadata) carried `_fallback: true` — a marker NO consumer recognized, `readServiceSelfInfo` included — so both discovery builders reported them as fully `available`. They were absent from D12's inventory and were its worst case. - plugin-dev marked every implementation with the same `_dev: true`, normalized to `{ status: 'stub', handlerReady: false }`. That declared a working in-memory search index exactly as fake as an AI stub returning invented text. The single marker is why #4058's real question — should the dispatcher gate other domains on `handlerReady`, as #4000 did for analytics? — could not be answered: the two kinds of implementation in there were indistinguishable. This change makes them distinguishable, and changes nothing else. The classification, now one reviewable table (`DEV_STUB_SELF_INFO`): - `degraded` — really does the work, with reduced capability. `file-storage` really stores and returns the bytes, `search` really indexes and matches, `metadata`/`workflow` really record and read back, `realtime` really delivers in-process, and the wrapped kernel fallbacks describe themselves. Their answers are true answers; each `message` names what is missing — including the ones that are easy to miss (memory-job's `schedule()` never fires: no timer; the workflow stub validates no state machine). - `stub` — the answer is fabricated: `ai` (invented text), `automation` (success without running the flow), `notification` (claims delivery, delivers nothing), `data` (accepts writes, stores none), `auth` (waves everyone through as an admin), `security.*` (allow-all). `handlerReady: false` answers the separate question — no HTTP handler serves this — and is set for every `stub` plus the surfaceless `degraded` ones (`cache` / `queue` / `job` / `realtime`, the D12 realtime precedent). Also: the one duck-typed consumer of the ad-hoc markers (an i18n diagnostic in app-plugin) now reads `readServiceSelfInfo`, and plugin-dev never overwrites a self-description an implementation already carries — a wrapped kernel fallback knows better than this plugin what it is. No routing, gating, or dispatch behavior changes: every dispatcher domain resolves services exactly as before. Discovery output does change, which is the point — a kernel fallback that claimed `available` now reports `degraded` and says why. `_dev` stays understood by `readServiceSelfInfo`, so third-party stubs carrying it keep working. #4058 step 2 (which domains adopt the `handlerReady` gate) stays open, now with the two classes separable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvcnzFr5XBuvvAd7mosG54
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 39 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 30, 2026
os-zhuang
marked this pull request as ready for review
July 30, 2026 09:08
os-zhuang
pushed a commit
that referenced
this pull request
Jul 30, 2026
…cile with #4082 (#4058 step 1) #4082 landed the classification half of #4058 while this branch was open, and did it better: `DEV_STUB_SELF_INFO` is one reviewable table, it covers the kernel's OWN fallbacks too (the `_fallback: true` marker no consumer recognized — filed from this branch as #4089, now fixed at the source), it drops the blanket `_dev: true` from plugin-dev rather than keeping it as a provenance tag, and it sets `handlerReady: false` on the surfaceless `degraded` fallbacks (`cache`/`queue`/`job`/`realtime`) — a refinement this branch missed. So this branch keeps only what #4082 explicitly left open: step 2, the gate. Dropped from this branch as superseded: - the per-stub `devDegraded`/`devStub` markers and their tests (main's table wins); - retiring the `automation` / `notification` / `ai` stubs. Step 1 classes them `stub`, so the gate already answers their HTTP surface as an empty slot without deleting anything — verified end to end below. Whether the fabricating class should be registered at all is the wider evaluation in #4093 (A tier), which is where that deletion belongs alongside `data`/`auth`/`security.*`. Kept: `isServiceServeable` + the six domain gates, the discovery `routes`/ `features` alignment, the metadata-protocol route suppression, their tests, and the ADR/docs/changeset text — the ADR annotation now reads as step 2 on top of step 1 instead of re-claiming the classification. The gate is compatible with step 1's table by construction, because it reads `handlerReady` and not `status`: `file-storage` / `i18n` / `metadata` / `workflow` declare `degraded` with `handlerReady` defaulting to true and keep serving, while the surfaceless `degraded` fallbacks have no dispatcher domain to gate. Verified against a real plugin-dev slate after the merge: `automation` / `notification` / `ai` are registered and classed `stub` ⇒ no route advertised, `GET /automation` and `GET /notifications` unhandled, `POST /ai/chat` 404, and `GET /ai/agents` still answers `{ agents: [] }`; `file-storage` / `i18n` / `workflow` / `metadata` keep their routes and serve. runtime 923, plugin-dev 8, objectql 1183, metadata-protocol 99, core 415 all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UNGqBQcdJ2RYHtWgntJ9B
This was referenced Jul 30, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…lerReady, not slot occupancy (#4058 step 2) (#4086) #4000 made the `/analytics` domain execute ADR-0076 D12's third conclusion ("consumers treat only `handlerReady: true` as a real capability"). Every other service domain still gated on "is the slot occupied", so an occupant that self-declares `handlerReady: false` was called like a real implementation and its fabricated answer went out as a 200. #4082 (step 1) made the two kinds of implementation distinguishable; this is the gate that reads the distinction. One predicate — `isServiceServeable` in `runtime/src/service-serveable.ts`, which absorbed `isAnalyticsServiceServeable` — is read by the `/analytics`, `/automation`, `/notifications`, `/ai`, `/storage` and `/i18n` domains, the route-mount gate, discovery's `routes`/`features`, and the metadata-protocol builder's route advertisement. A slot whose occupant declares `handlerReady: false` gets exactly the answer an empty slot gets: the domain's own 404, or the explicit 501 `/storage` and `/i18n` use. Same predicate on the advertisement and the handler, so the two cannot disagree. `handlerReady`, not `status`, is the test, and that is what makes the rule safe to generalize: an implementation that really works declares `degraded`, which defaults `handlerReady` to true, so `file-storage` / `i18n` / `metadata` / `workflow` keep serving. Only the fabricating class is answered as empty. `discovery.services.*` stays presence-gated: a registered stub still reports `{ enabled: true, status: 'stub', handlerReady: false }` with no `route`, which says strictly more than collapsing it to `unavailable` would. `/ai` improves for the stub case. Being truthy, such an occupant used to fall through to the `!routes` 503 ("AI service routes not yet initialized") and lose the `GET /ai/agents` empty-list answer the console polls for on every navigation. Both are restored. Deliberately unchanged: - `/data`, `/meta`, `/auth` and the security path are NOT gated. Their dev stubs back the dev stack's own core loop, and gating them would 404 the dev stack itself. Step 1 labels them honestly either way. - No stub is deleted. #4058 preferred retiring the `automation` / `notification` / `ai` stubs too, but once step 1 classes them `stub` this gate already makes their HTTP surface equal to an empty slot — verified end to end — so "should the fabricating class occupy a slot at all" became separable. It is evaluated as a class in #4093, together with `data` / `auth` / `security.*`. - The metadata-protocol route suppression covers only the six dispatcher-owned slots. Elsewhere the route belongs to the plugin that registers the service, so `handlerReady` says nothing about whether THAT route is mounted; suppressing it would be a guess. A test pins that boundary. Filed out of scope (Prime Directive #10): #4087 (the `/storage` bridge calls `upload(file, {request})` against the contract's `upload(key, data, options?)` — pre-existing, reproduced during end-to-end verification) and #4093 (whether the "stub fills the slot" design should be retired wholesale). #4089 (the kernel fallbacks' unrecognized `_fallback` marker, filed from this branch) was fixed at the source by #4082 and is closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 30, 2026
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.
#4058 的第一步(分类),不动任何门。#4062 落地后的自然延续。
为什么先做这一步
#4058 真正要回答的是"其他 dispatcher 域要不要照 analytics 那样加
handlerReady门"。这个问题之前答不了,因为 plugin-dev 里所有实现共用一个_dev: true,被归一化成{ status: 'stub', handlerReady: false }—— 它把"真能用的内存实现"和"编造答案的假实现"宣布为同一种东西。分不开,就没法逐域取舍。盘的过程中还捞出一条比 plugin-dev 更严重、且不在 D12 原始清单里的:内核自己的 fallback(
createMemoryCache/Queue/Job/I18n/Metadata,由 ObjectKernel 自动注册)带的是_fallback: true—— 这个 key 没有任何 consumer 认识,readServiceSelfInfo也不认。于是两个 discovery builder 都把它们报成完整可用的available。D12 要修的那个谎,在内核自己的兜底实现上一直没被修到。分类规则
判据只有一条:答案是不是真的。
degraded—— 真干活,能力打折:cache/queue/job/file-storage/search/i18n/metadata/workflow/realtime。storage 真的存进去也真的取得回来,search 真的建索引真的匹配,metadata 真的注册真的列得出。把它们说成stub是低估,会让 consumer 放弃一个其实能用的能力。每条的message点名缺的是什么 —— 包括容易漏看的两处:memory-job的schedule()永远不会自己触发(没有 timer,只有trigger()真跑),workflow stub 不校验任何状态机(每次 transition 都接受)。stub—— 答案是编的:ai(编造文本)/automation(不跑 flow 直接报成功)/notification(声称已送达,实际什么都没发)/data(收下写入,一行不存)/auth(一律放行为固定 dev admin)/security.*(allow-all)。这些绝不能被当成能力 —— D12 里记的"已经误导过 agent"就是 AI stub。handlerReady回答的是另一个问题(有没有 HTTP handler 真的在服务):所有stub为 false,加上没有 HTTP 面的degraded(cache/queue/job/realtime—— realtime 是 D12 立的先例)。分类写在一张表里(
DEV_STUB_SELF_INFO),因为这次改动的产物就是这张表,它应该能被一眼 review。其他
createMemoryCache等)比这个插件更清楚自己是什么,applySelfInfo见到已有__serviceInfo就不动。svcAny._fallback || svcAny._dev)改读readServiceSelfInfo。content/docs/kernel/services.mdx的"可选服务"示例原来用getServices().has('analytics')+ 虚构的analytics.track():既不是真契约,又恰好示范了 D12 要 consumer 改掉的"只判存在"。换成真契约,并补了一段"presence is not capability"说明怎么读__serviceInfo。影响面
不改任何路由/门/dispatch 行为 —— 每个 dispatcher 域解析服务的方式和之前完全一样。变的是 discovery 输出:以前谎称
available的内核 fallback 现在报degraded并说明原因;以前被一律打成stub的 storage/search/metadata 等现在报degraded。_dev仍被readServiceSelfInfo认识,第三方带这个标记的 stub 不受影响。测试
core/fallbacks:每个内核 fallback 都必须能通过标准 descriptor 读到degraded+ message(旧断言测的是_fallback === true)。plugin-dev:新增一条把整张分类表钉住的测试 —— 9 个degraded/ 8 个stub逐个断言 status、message、以及 stub 的handlerReady: false;另外钉住"无 HTTP 面的 degraded 也是 false"、无 factory 的ui占位符仍诚实、以及 plugin-dev 的 analytics dev stub 仍会填满槽位,dispatcher 不检查 handlerReady —— #3891 退役 shim 后同形状的最后一处残留(dev-only) #4000 的 analytics 空槽位。objectql集成测试里那条_fallback断言改读 descriptor。core(416) /plugin-dev(8) /objectql(1180) /runtime(913) /spec(6937) /metadata-protocol(99) /rest(505),加 lint 与 3 个 guard。runtime的datasource-autoconnect.test.ts > makes the federated object queryable…。单独跑必现(3/3),跑整包时偶尔能过;ext_note读到 6 行(单独跑时 12 行)而非 2 行。与本改动无关(改动前后一致,回退driver-memory到 #4048 前也一样),main 的 CI 是绿的。已另开 issue 记录。Generated by Claude Code