Skip to content

org 作用域的 flow overlay 只在「本进程内发布后」绑定触发器,重启后静默失绑——冷启动两条读路径都把 organization_id 非空的行滤掉了 #6190

Description

@hotlong

发现于 #6155 的前提核验(未在该 PR 内修,按 Prime Directive #10 单开)。

事实(实测,非推断)

flowDEFAULT_METADATA_TYPE_REGISTRY 里是 allowOrgOverride: true + allowRuntimeCreate: true,所以 runtime/src/domains/meta.ts:158-159saveMetaItem({ type, name, item, organizationId })(org 取 resolveActiveOrganizationId)会写出 sys_metadata.organization_id = 'org_a' 的 flow 行。实测确认该行确实落库。

之后这一行的命运是不对称的:

时机 路径 org 行可见?
发布当场(同进程) saveMetaItemapplyRegistryWriteThrough(publish 分支)→ hydrateOverlayIntoRegistry → 进程级 SchemaRegistry ✅ 可见
发布当场,自动化重绑 emitMetadataMutationmetadata:reloadedresyncFlowsFromProtocolgetMetaItems({ type: 'flow' })(读到 registry 分支) ✅ 绑定,定时任务起飞
重启后冷启动 ObjectQLPlugin.restoreMetadataFromDbprotocol.loadMetaFromDb(),where = { state:'active', organization_id: null }(metadata-protocol/src/protocol.ts:10620-10623) ❌ 跳过
重启后 kernel:ready 绑定 service-automation/src/plugin.ts:1542 protocol.getMetaItems({ type: 'flow' }) —— 不传 organizationId,于是 protocol.ts:3319orgRecords = orgId ? … : [] 为空 ❌ 跳过

实测(真 ObjectStackProtocolImplementation + 内存 sys_metadata 引擎,两行 flow:org_sweep org=org_a、platform_sweep org=null):

PROBE rows = [{"name":"org_sweep","org":"org_a"},{"name":"platform_sweep","org":null}]
PROBE getMetaItems({type:flow}) names(registry 为空时) = ["platform_sweep"]
PROBE getMetaItems({type:flow}) names(publish 写穿后)  = ["org_sweep","platform_sweep"]
PROBE loadMetaFromDb = {"loaded":1,...}      // 只有 platform_sweep

影响

  1. 静默失效:租户在 Studio 里发布一个 org 归属的 flow(record-change 或 schedule),当天正常触发;下一次进程重启后它再也不触发,没有任何日志说它消失了——kernel:bootstrapped 的 unbound 审计也看不见它(它压根没注册)。这正是 AGENTS.md「Absence must be loud」的反面。
  2. 可见性不对称:写穿期间该 org 行进了进程级 registry,于是 getMetaItems({ type: 'flow' })任何 org 的调用方都返回它——而 loadMetaFromDb 的注释明确说 per-org overlay 之所以不 hydrate 就是「to avoid cross-org leakage into the process-wide SchemaRegistry」。写路径比读路径更宽松,恰好是 applyRegistryWriteThrough TSDoc 自己声明要避免的("The write must not be more permissive about that than the read is" —— 该 gate 只挡了 environmentId !== undefined,没挡 org)。

#6155 的关系

不是 #6155 的子问题:#6155 问的是「定时流程的 org 从哪来」,本单问的是「org 归属的 flow 到底还活不活」。但两者相互制约——若维护者裁决 org flow 本就不应被加载(ADR-0005 的白名单表原文即「automation flow ❌ per-org override,Per-org variants are a deployment, not an overlay」,与代码现状矛盾,另见同批开的 registry/ADR 漂移单),那本单的修法就是在写入侧拒绝而不是在读取侧补齐。所以建议先定性再定修法。

复现

无需真 DB:packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts 的 stub 引擎即可(需给 registryisPackageDisabled: () = false 与一个真存的 listItems)。

Refs:#6155#4636(loadMetaFromDb object 分支的另一处 row 读取缺陷)、ADR-0005 §Tenant-customizable type whitelist、applyRegistryWriteThrough(protocol.ts:7434)。

Blocked-by: #6191


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions