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

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions