fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus - #14228

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard
Sep 2, 2026
Merged

fix(service-cluster): stop the metadata bridge reporting “bridged” over an in-process cluster bus#14228
os-steve merged 2 commits into
mainfrom
claude/issue-14021-metadata-bridge-inprocess-guard

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14021

MetadataClusterBridgePlugin lane 1 attached and then logged
bridged metadata.changed → cluster.pubsub with no driver check. Runtime
registers the memory driver by default, so on an ordinary single-process boot
a cluster service IS present and that line is a false positive: the bus
keeps its state inside one process and the fan-out it announces reaches nobody.


Step zero — re-measurement on the post-1403d943a tree

⛔ Nothing below is copied from the card. PR #14183 (card #13331) landed at
1403d943a and changed this bridge, so all three were measured fresh on that
tree. One half of the card was already absorbed by #14183; the other stands.

1. The test file now EXISTS — what did #14183 already cover?

packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts
was created by #14183 (9 tests). Against the card's three wanted cases:

card's casestatus before this PRwhere
attach with (pubsub, nodeId)already covered by #14183a manager metadata service and a real protocol both attach
attachClusterPubSub-missing warnalready covered by #14183, asserted verbatimwarns for lane 1 AND attaches lane 2 in the same boot
in-process (lane 1)NOT covered — deliberately left opensee below

Card half 2 shrinks to one case, not three.#14183 did not merely omit
lane 1's in-process behaviour, it declined it in writing and said why — the
test file's own header:

⚠️ Lane 1's in-process-driver behaviour (it attaches and logs "bridged" on the
memory driver that fans out to nobody) is #14021's card, NOT pinned here —
these cases drive lane 1 only through its warn/absence paths so that card
stays free to fix it
.

2. Does half 1 still hold? — YES, unchanged

attachMetadataServiceLane still attaches and logs info unconditionally.
#14183 did not incidentally fix it. It went further and recorded the defect
while adding lane 2 beside it:

Guarded on isInProcessClusterDriver from birth (the shape
AuthzClusterBridgePlugin uses): the in-process memory driver fans out to
nobody, so "attached" there would be the misreading #14021 records for lane
1's info line
— a new lane does not inherit a known defect.

#14183 built the correct shape 40 lines below the defect, in the same file,
and named this card as the owner of the older one.

3. Is the sibling's guard shape still in place? — YES

authz-cluster-bridge-plugin.ts:78-84 still consults
isInProcessClusterDriver, still exported from ./split-brain-guard.js
(split-brain-guard.ts:42), still imported by both bridges.

Verdict: the card stands, with half 2 reduced to the single in-process case.


The attach fork — ⚠️ answered by measurement, not by preference

The order required determining whether the sibling's guard changes only the log
claim or also skips the attach. Both in-tree exemplars skip the attach:

  • AuthzClusterBridgePluginif (isInProcessClusterDriver(...)) { bus = 'in-process'; } in the if, with attachAuthzInvalidationPubSub called only in the else.
  • MetadataClusterBridgePlugin.attachProtocolLane (lane 2, this same file) — logs at debug and returns before the attach.

⇒ Lane 1 now does the same. This PR does not invent a third shape.

The fix

isInProcessClusterDriver(cluster.driver) is consulted before attaching, and
the in-process case is stated at debug in lane 2's existing wording, adapted
only for which channel it names:

MetadataClusterBridgePlugin: cluster driver "memory" is in-process; metadata.changed fan-out has no peers to reach, skipping

⛔ No new sink (ctx.logger.debug, as lane 2). ⛔ No level raised.

Guard position is load-bearing. It sits after the attachClusterPubSub
feature-detect, exactly as in lane 2, so #13331's original boot symptom still
fires byte-for-byte on an in-process boot. Pinned by its own test — fixing a
false positive must not buy a true negative.

Why skipping the attach loses nothing observable. Measured, not assumed:
the only subscriber of metadata.changed anywhere in the tree is the same
MetadataManager that publishes it (metadata-manager.ts:2951; grep over
the repo finds no other subscribe), and its loopback guard drops every
message whose originNode equals its own node id. On an in-process bus that is
every message. (meta-overlay-cache.ts:80-86 and protocol.ts:3999 both
state in-source that they deliberately do not subscribe to this channel.)

Tests

4 added, alongside #14183's 9. Package suite 97 passed (97) at 49bab8dd9.

Ablation

Guard deleted from lane 1; mutation proven on disk before the run — HEAD
blob b7d4d18b vs mutated a7b72067, marker count 1 ⇒ 0, guard call sites
2 ⇒ 1 (lane 2's survives):

Tests 2 failed | 11 passed (13)
× skips the attach and never claims “bridged” on the memory driver
× leaves nothing to detach when the in-process guard skipped the attach

⇒ the in-process pins go red, and the cross-process control plus the
warn-ordering pin stay green — the predicted direction. Restore proven by
state
, not by an exit code: git diff HEAD empty, worktree blob back to
b7d4d18b, markers 1 / 2. The mutation reached the test without a rebuild
because the subject is imported relatively (./metadata-cluster-bridge-plugin.js),
so vitest resolves it from source, not through dist/ — proven by the run
reddening at all. The script carried an absolute-path trap ... EXIT INT TERM.

Clause-② — both limbs, neither defaulted

1. Path limb — no. Derived live, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack reports
"no path-derived mandate: the surface hits none of the 3 declared glob(s)".
The contract surface is packages/spec/src/** (SUSPECT_TIER_GLOBS); this diff
touches packages/services/service-cluster/src/**, content/docs/kernel/**
and .changeset/.

2. Content limb — no, and this is the limb the fork could have flipped, so
it is argued rather than asserted. The criterion is "changes contract
accept/reject behaviour or widens the public surface"
:

Gates

Derived live at the final head with --repo asserted; re-derived after the
docs commit, which pulled in 22 further families
(the first derivation did not
name them). All green except two, both NOT MEASURED by their own verdict
lines — never read from a bare exit code:

  • check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log that CI tees and this run has none. Its own text: "⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: needs a full pnpm build; 72+ packages have no dist/. Whole-repo, and CI runs it regardless.
  • check:type-check-debt — exit 3 (whole-ledger --re-measure wants 52 built dependency closures). Narrowed and measured for this package instead:tsc --noEmit over @objectstack/service-cluster reports exactly 1 error, TS2322 in src/memory/memory.contract.test.ts — the pre-existing ledger entry (errors: 1, code-tier 1 (TS2322)), unmoved. --listFiles confirms both edited files are in that program, so the reading actually covers them.
  • check:skill-examples — first run exit 1, but its verdict line reads PREREQUISITE NOT MET (unbuilt client-react declarations), not a finding — the exit-1-not-3 shape. Built the closure and re-ran: green, 259 prose examples across 3 surfaces.

pnpm lint (repo-wide eslint . --no-inline-config) was run whole, not
narrowed: exit 0. check:type-check-coverage (structural half) green.
Ratchet families re-run at the final head 49bab8dd9 after the docs commit.

Bounded in-scope docs correction

Two sentences describing this bridge's precondition would, after the guard,
assert exactly the fan-out the guard exists to stop claiming
(content/docs/kernel/cluster.mdx §6.2 and
content/docs/kernel/services-checklist.mdx). Same defect class, one sentence
each, mechanical, and no open PR claims either file (checked across all 10 open
PRs). Named here rather than left as a silent rider. ⛔ content/docs/releases/
untouched.

Boundaries honoured

#13331 / PR #14183's work not touched · ⛔ authz-cluster-bridge-plugin.ts
unmodified (it is the exemplar) · ⛔ #11968 not reopened · ⛔ content/docs/releases/
untouched · ⛔ not flipped to ready, no auto-merge.

Generated by Claude Code


Generated by Claude Code

…rocess cluster driver
`MetadataClusterBridgePlugin`'s `metadata.changed` lane attached and logged
"bridged metadata.changed -> cluster.pubsub" unconditionally. `Runtime`
registers the `memory` driver by default, so on an ordinary single-process
boot a cluster service IS present and that claim is a false positive: the bus
keeps its state inside one process and fans out to nobody.
Lane 1 now consults `isInProcessClusterDriver(cluster.driver)` before
attaching and states the in-process case at `debug` -- the shape
`AuthzClusterBridgePlugin` uses and the one this plugin's own lane 2 was born
with. Both skip the attach rather than relabel the log, and so does this.
Skipping reaches nothing: the only subscriber of `metadata.changed` in the
tree is the same `MetadataManager` that publishes it, behind an `originNode`
loopback guard that discards every in-process message.
The seam-missing warn keeps its position ahead of the driver guard, so
#13331's original boot symptom still fires verbatim on an in-process boot, and
no log level is raised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… described
Both places that describe `MetadataClusterBridgePlugin`'s precondition said it
calls `attachClusterPubSub()` on `kernel:ready` full stop, which after the
lane-1 driver guard would assert the very fan-out the guard exists to stop
claiming. Same defect class, one sentence each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 1, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-cluster, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/cluster.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx(via MetadataClusterBridgePlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacdpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 0b39d22f757679919f2904db0f61e82275513c75 — the merge of head 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 into base 66ecc50a91ab66e254032f1589bf3200da27cacd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0b39d22f757679919f2904db0f61e82275513c75 && git checkout 0b39d22f757679919f2904db0f61e82275513c75
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66ecc50a91ab66e254032f1589bf3200da27cacd 49bab8dd9ecde3389b0f56d014275aa7a44e07a3 && git checkout -B drift-repro 66ecc50a91ab66e254032f1589bf3200da27cacd && git merge --no-ff 49bab8dd9ecde3389b0f56d014275aa7a44e07a3
node scripts/docs-audit/affected-docs.mjs --json 66ecc50a91ab66e254032f1589bf3200da27cacd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66ecc50a91ab66e254032f1589bf3200da27cacd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

PM 复核 — ACCEPT。⛔ 无需契约复审

domain:services 执行席 #6021。governed 判据在 origin/main 树上重跑:0 of 5,未被治理

⭐ 重测做对了 —— 三种结局你测出了「半 1 成立 / 半 2 缩小」

派发令要求对 1403d943a 之后的树重测,⛔ 不许照抄卡。你测出的分解比卡本身准确:

⇒ ⭐ 而你改了那段文件头,让它陈述「已修、钉在本文件底部」。⛔ 没留下一句在修完之后变成假话的散文 —— 这正是本仓反复立卡的那一类腐烂,你在制造它之前就掐了。

⭐ attach 分叉:测出来的,不是选出来的

派发令要求「照姊妹的实际做法做,⛔ 不要凭听起来更对自选一个」。你测了两个在树样板 —— AuthzClusterBridgePlugin 只在 else 分支 attach,lane 2 在 attach 前 return —— 两者都跳过 attach,于是 lane 1 照做。⛔ 没有发明第三种形状。

⭐ 行为确实移动了,而你证明了它不可观测 —— 这一句我独立复核过

跳过 attach 意味着 in-process 下不再挂载,这是行为位移,你如实声明了。让它安全的是那句证明,我对树核过,成立:

主张复核
metadata.changed 在全树唯一的订阅者就是发布它的 MetadataManagermetadata-manager.ts:506 定义 CLUSTER_CHANNEL;:2847 发布并写入 originNode: this.clusterNodeId;:2906 订阅。其余命中全是注释/示例;meta-overlay-cache.ts:80 更明写它订阅该频道
loopback 守卫丢弃每一条 in-process 消息:2906if (p?.originNode && p.originNode === this.clusterNodeId) return;,而 in-process 总线上每条消息的 originNode 都等于自己

被移除的订阅本来就永远送不到任何东西。 ⇒ 可观测的 accept/reject 未动 ⇒ Clause-② no 成立,⛔ 不欠 needs:contract-review

⚠️ 等级也对:in-process 那支是 debug,与 lane 2 一致,没有抬级、没有新 sink、没有新措辞#13398 的类级裁决(已发布 sink 形状不得抬到 error)不被触碰。

⭐ 顺序钉 —— 这一条最容易被漏掉

守卫放在 seam-missing warn 之后,并且你为此单独下了一枚钉:#13331 的原始 boot 症状在 in-process boot 上仍逐字触发。

Fixing a false positive must not cost a true negative.

⇒ 一个只看「假阳性没了」的实现会把守卫放到 warn 前面,然后静默掉一个真阳性。你钉住了它。

反向对照(cross-process 驱动attach 且逐字打印 bridged)也在,消融 2 红 11 绿方向吻合、盘上证实、按状态恢复。

⚠️ 两条你捎回的平台读数,已进座位贴账本

  1. 加了 docs 提交之后重新推导门族,多出 22 个家族 —— 第一次推导没有命名它们。⇒ 改动集变化后必须重推导,⛔ 不能沿用开工时的那份。
  2. ⚠️check:skill-examples 也用 exit 1 报「前置未满足」 —— 与本轮先前测到的 check:i18n 同形。⇒ 这已经是第二个不守 exit-3 约定的门,「读 verdict 行、永不读裸退出码」从个例升级为通则

check:type-check-debt 的整账重测需 52 个已构建闭包,你改为对本包窄化实测(1 个 TS2322,正是既有台账条目,未上移),并用 --listFiles 证实两个改动文件都在该 program 内 ⇒ 是读数,⛔ 不是排除掉测试文件之后的幻绿。

待办

CI 出结论且全绿后由本席转 ready + 挂 auto-merge。⛔ 你不必再动手。


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 2, 2026 00:49
@os-steve
os-steve added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit a59f78dSep 2, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-14021-metadata-bridge-inprocess-guard branch September 2, 2026 01:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude