发现于 #6155 的前提核验(未在该 PR 内修,按 Prime Directive #10 单开)。
事实(实测,非推断)
flow 在 DEFAULT_METADATA_TYPE_REGISTRY 里是 allowOrgOverride: true + allowRuntimeCreate: true,所以 runtime/src/domains/meta.ts:158-159 的 saveMetaItem({ type, name, item, organizationId })(org 取 resolveActiveOrganizationId)会写出 sys_metadata.organization_id = 'org_a' 的 flow 行。实测确认该行确实落库。
之后这一行的命运是不对称的:
| 时机 |
路径 |
org 行可见? |
| 发布当场(同进程) |
saveMetaItem → applyRegistryWriteThrough(publish 分支)→ hydrateOverlayIntoRegistry → 进程级 SchemaRegistry |
✅ 可见 |
| 发布当场,自动化重绑 |
emitMetadataMutation → metadata:reloaded → resyncFlowsFromProtocol → getMetaItems({ type: 'flow' })(读到 registry 分支) |
✅ 绑定,定时任务起飞 |
| 重启后冷启动 |
ObjectQLPlugin.restoreMetadataFromDb → protocol.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:3319 的 orgRecords = 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
影响
- 静默失效:租户在 Studio 里发布一个 org 归属的 flow(record-change 或 schedule),当天正常触发;下一次进程重启后它再也不触发,没有任何日志说它消失了——
kernel:bootstrapped 的 unbound 审计也看不见它(它压根没注册)。这正是 AGENTS.md「Absence must be loud」的反面。
- 可见性不对称:写穿期间该 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 问的是「定时流程的 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 引擎即可(需给 registry 补 isPackageDisabled: () = 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
发现于 #6155 的前提核验(未在该 PR 内修,按 Prime Directive #10 单开)。
事实(实测,非推断)
flow在DEFAULT_METADATA_TYPE_REGISTRY里是allowOrgOverride: true+allowRuntimeCreate: true,所以runtime/src/domains/meta.ts:158-159的saveMetaItem({ type, name, item, organizationId })(org 取resolveActiveOrganizationId)会写出sys_metadata.organization_id = 'org_a'的 flow 行。实测确认该行确实落库。之后这一行的命运是不对称的:
saveMetaItem→applyRegistryWriteThrough(publish 分支)→hydrateOverlayIntoRegistry→ 进程级 SchemaRegistryemitMetadataMutation→metadata:reloaded→resyncFlowsFromProtocol→getMetaItems({ type: 'flow' })(读到 registry 分支)ObjectQLPlugin.restoreMetadataFromDb→protocol.loadMetaFromDb(),where = { state:'active', organization_id: null }(metadata-protocol/src/protocol.ts:10620-10623)service-automation/src/plugin.ts:1542protocol.getMetaItems({ type: 'flow' })—— 不传organizationId,于是protocol.ts:3319的orgRecords = orgId ? … : []为空实测(真
ObjectStackProtocolImplementation+ 内存 sys_metadata 引擎,两行 flow:org_sweeporg=org_a、platform_sweeporg=null):影响
kernel:bootstrapped的 unbound 审计也看不见它(它压根没注册)。这正是 AGENTS.md「Absence must be loud」的反面。getMetaItems({ type: 'flow' })对任何 org 的调用方都返回它——而loadMetaFromDb的注释明确说 per-org overlay 之所以不 hydrate 就是「to avoid cross-org leakage into the process-wide SchemaRegistry」。写路径比读路径更宽松,恰好是applyRegistryWriteThroughTSDoc 自己声明要避免的("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 引擎即可(需给registry补isPackageDisabled: () =false 与一个真存的listItems)。Refs:#6155、#4636(
loadMetaFromDbobject 分支的另一处 row 读取缺陷)、ADR-0005 §Tenant-customizable type whitelist、applyRegistryWriteThrough(protocol.ts:7434)。Blocked-by: #6191
Generated by Claude Code