Skip to content

fix(runtime,metadata-protocol): 两个 discovery builder 计算 metadata 槽位,不再互相矛盾 (#4089) - #4114

Merged
os-zhuang merged 3 commits into
mainfrom
claude/core-fallback-i18n-issue-2sdl2a
Jul 30, 2026
Merged

fix(runtime,metadata-protocol): 两个 discovery builder 计算 metadata 槽位,不再互相矛盾 (#4089)#4114
os-zhuang merged 3 commits into
mainfrom
claude/core-fallback-i18n-issue-2sdl2a

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Closes #4089

背景:#4089 的两半,第一半已由 #4082 落地

issue 提的"需要的决定"选了第 1 条(给五个 core fallback 加 __serviceInfo),这部分在 #4058 step 1 / PR #4082 里已经做完了 —— _fallback: true 全部换成 __serviceInfocreateMemoryMetadata / Cache / Queue / Job / I18n 现在都自称 degraded。issue 标题里的 i18n fallback 也随之被通用循环正确读到。

剩下的是 issue 最后一段那条核对

dispatcher 的 discovery 已经给 metadata 硬编了 degraded + "In-memory registry; DB persistence pending",而 metadata-protocol 那侧的 builder 硬编了 available。两个 builder 对同一个槽位说法不同,也一并在这条里核对。

核对结果:这不只是"说法不同",而是 D12 的标记机制在这个槽位上根本没被执行过metadata 不在读 __serviceInfo 的那个循环里,而在循环上方一个 "kernel-provided (always available)" 硬编块里,两个 builder 各写一份,且方向相反:

builder 硬编值 谁被说错
runtime/http-dispatcher.ts:997 永远 degraded + "DB persistence pending" 装了 MetadataPlugin、sys_metadata 真落库的栈被说成没有持久化
metadata-protocol/protocol.ts:1407 永远 available 跑内核内存 fallback 的栈被说成与持久化注册表完全等价(#4089 报的那半)

所以每个 builder 各对一半的栈说错,方向还相反 —— 同一个 discovery.services.metadata,取决于哪个 host 回答,消费者拿到两个不同答案。#4082 刚给 createMemoryMetadata 加上的标记在两边都没人读,因为两边都没读任何东西。

改动

两个 builder 都改成读注册进槽位的实现自己的 __serviceInforeadServiceSelfInfo):

  • 内核内存 fallback、plugin-dev 的 dev registry → status: 'degraded' + 该实现自己的 message(写明缺什么、装什么补上),而不是一句通用文案;
  • MetadataPlugin,或任何不带标记的实现 → available,无 message;槽位为空也走这一支(/meta 由 protocol 提供,与该服务在不在无关)。

handlerReady: true 两边都无条件写死,且刻意不从自述里取:它回答的是"/api/v1/meta 挂没挂",而这条路由由 protocol 提供 —— 槽位里放一个 degraded 实现不会把它卸掉。(对比 realtime:D12 在那里钉 handlerReady: false,因为那边根本没有 HTTP surface。)这也正是 #4089 里"门只读 handlerReady,修这个标记不会改变任何路由行为"那条判据成立的原因。

data 保留硬编,附理由:ObjectQL 是它唯一的 producer,而 plugin-dev 总是把 ObjectQLPlugin 作为子插件加载(dev-plugin.ts:523-525),所以在任何能构建 discovery 的栈里,plugin-dev 的 data stub 都进不了槽位。不过这条"真话"来自加载顺序约定而非构造保证,按 Prime Directive #10 单独记为 #4130(含两条候选决定),不在本 PR 扩范围。

不涉及任何路由 / 门控 / dispatch 行为:svcAvailable 和各 domain 的 predicate 一行未动。

测试

  • packages/runtime/src/http-dispatcher.test.ts — 两个方向各一条(真实 createMemoryMetadatadegraded + 它自己的 message;无标记服务 ⇒ available 且无残留的 "persistence pending"),外加这次真正要钉住的不变量:同一个服务实例交给两个 builder,status / handlerReady / message / route 必须一致。
  • packages/objectql/src/protocol-discovery.test.ts — metadata-protocol 侧同样两个方向。
  • packages/objectql/src/plugin.integration.test.ts — 端到端:真实 ObjectKernel + ObjectQLPlugin(不带 MetadataPlugin),内核注入 fallback → protocol shim 走活的服务注册表 → discovery 必须说 degraded。自述标记只有在 builder 真去读的时候才有用,这条钉的就是整条链。

已跑通:runtime(65 files) metadata-protocol(17) objectql(80/1183 tests) spec(269) core(26) rest(35) plugin-hono-server(12) plugin-dev metadata hono 全绿;改动文件 eslint 干净,http-dispatcher.ts / protocol.ts 无新增类型错误。

文档 / changeset

  • docs/adr/0076-objectql-core-tiering.md — D12 inventory 里给 svcAvailable 那条加 core 的内存 fallback 带 _fallback: true 而非 __serviceInfo,readServiceSelfInfo 不识别 —— AppPlugin 注册的 i18n fallback 在 discovery 里报 available #4089 update:标记被尊重,但有两条 entry 从来没走过它。
  • content/docs/kernel/services-checklist.mdx — discovery 样例里那句旧文案就是这次删掉的硬编,改成 fallback 的真实输出并说明它是"计算出来的"。
  • content/docs/api/index.mdx — 样例里的 available 明确成 MetadataPlugin 的情形(该页把样例框定为 minimal install,而 minimal install 现在报 degraded)。
  • content/docs/api/client-sdk.mdxcontent/docs/releases/implementation-status.mdx — docs-drift 报的 19 篇里真正重复了旧硬编的两篇:SDK 样例无条件打印 metadata: degraded;status 表还写着"in-memory only, DB persistence pending"(sys_metadata 持久化早已随 MetadataPlugin 发布)。该行仍保留 ⚠️ —— 剩下的 gap(多实例同步、迁移工具、热重载)与持久化无关。
  • changeset:discovery-metadata-slot-computed.md(两个包 patch;discovery 输出对消费者可见)。

claude added 2 commits July 30, 2026 10:17
…e `metadata` slot instead of contradicting each other (#4089)

ADR-0076 D12's rule is that a service self-identifies (`__serviceInfo`) and the
discovery builder reports what it declares. #4058 step 1 gave the kernel's
`createMemoryMetadata` that marker. It went unread anyway: `metadata` was not in
the loop that reads markers, but in a "kernel-provided (always available)" block
above it — hardcoded separately in each builder, and the two hardcodes said
opposite things about the same slot.

- `runtime/http-dispatcher.ts`: permanently `degraded` + "In-memory registry; DB
  persistence pending". A stack with MetadataPlugin and a real `sys_metadata`
  table was reported as having no persistence.
- `metadata-protocol/protocol.ts`: permanently `available`. A registry that never
  reaches disk was reported as indistinguishable from the persisted one — the
  over-declaration #4089 filed.

So each builder was wrong for half the stacks, in opposite directions, and a
consumer reading `discovery.services.metadata` got a different answer depending
on which host answered.

Both now read the registered implementation's self-description: `degraded` plus
that implementation's own message (which names what is missing and what to
install) for the kernel fallback and plugin-dev's dev registry; `available` with
no message for MetadataPlugin, or any implementation carrying no marker — the
absent-service case included, since the protocol serves `/meta` regardless.

`handlerReady: true` is stated unconditionally on both sides, deliberately NOT
taken from the self-description: it answers "is `/api/v1/meta` mounted?", and
that route is served by the protocol, so a degraded service in the slot does not
unmount it. (Contrast `realtime`, where D12 pins `handlerReady: false` because no
surface exists at all.) `data` keeps its hardcode: ObjectQL is its only producer,
and plugin-dev — which loads ObjectQLPlugin as a child — can never leave its
`data` stub in the slot on a host that builds discovery.

No routing, gating or dispatch behavior changes; `svcAvailable` and every domain
predicate are untouched. Tests pin both directions per builder, the real
kernel-boot path end-to-end (ObjectQLPlugin with no MetadataPlugin ⇒ `degraded`),
and — the invariant this closes — that the two builders now answer the same
service instance identically.

Closes #4089

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfdfmzBq2hdp6gzHaK1S9p
…vailable` describes

The `services.metadata` entry is computed now (#4089), so a sample showing
`available` is specifically the MetadataPlugin case — a minimal install running
the kernel's in-memory fallback reports `degraded` with a message instead.

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

vercel Bot commented Jul 30, 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 Jul 30, 2026 10:22am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/runtime.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)

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.

The two docs the drift check flagged that really repeated the removed hardcode
(#4089): the client-SDK sample printed `metadata: degraded` unconditionally, and
the implementation-status table still said the metadata system was in-memory only
with DB persistence pending — `sys_metadata` persistence ships via MetadataPlugin,
and the in-memory registry is the fallback for stacks without it. The row keeps
its ⚠️: the remaining gaps (multi-instance sync, migration tooling, hot reload)
are unrelated to persistence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfdfmzBq2hdp6gzHaK1S9p
@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 10:45
@os-zhuang
os-zhuang merged commit 4f30943 into main Jul 30, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/core-fallback-i18n-issue-2sdl2a branch July 30, 2026 10:46
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…ory, not spot checks (#3898) (#4306)

The three holes #3898 inventoried are all closed on main — #4082 eliminated
the `_fallback` marker by moving every kernel fallback onto the standard
`__serviceInfo` descriptor, #4114 made both discovery builders compute the
`metadata` slot, #4141 did the same for `data`. What remained was the issue's
suggestion 4: a recurrence gate over the known-fake INVENTORY rather than
per-slot spot checks.

This adds that gate to both discovery builders' suites: iterate
CORE_FALLBACK_FACTORIES (the complete fake inventory now that plugin-dev's
stub table is retired, ADR-0115), register each product into its own slot,
and assert discovery reports it `degraded` — never `available`. Table-driven,
so the next fallback added to the table is gated the day it lands.

cache/queue/job had no per-slot pin before this: dropping their
`svcAvailable(…, svc)` third argument — the exact #4130 regression shape —
was test-invisible. Verified the gate bites by simulating that regression
(`services.cache.status: expected 'available' to be 'degraded'`).

Closes #3898

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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

2 participants