Skip to content

ADR-0005 的租户可定制白名单表说 flow/workflow/approval 一律 ❌ per-org override,注册表却写着 allowOrgOverride: true——反转没有走 ADR(Prime Directive #13) #6191

Description

@hotlong

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

两处直接冲突

ADR-0005(accepted,docs/adr/0005-metadata-customization-overlay.md,§"Tenant-customizable type whitelist (shared-DB tenancy invariant)")的表格原文:

Domain Type Per-org override? Why
automation flow, workflow, approval ❌ Carry execution side-effects (events, jobs, audit). Per-org variants are a deployment, not an overlay.

同一节还写明「The authoritative whitelist lives on MetadataTypeRegistryEntry.allowOrgOverride」「The runtime validator (OVERLAY_ALLOWED_TYPES) derives its set from that flag — there is no parallel allowlist」。

代码(packages/spec/src/kernel/metadata-plugin.zod.ts,DEFAULT_METADATA_TYPE_REGISTRY):

{ type: 'flow', label: 'Flow', …, supportsOverlay: false, allowOrgOverride: true, allowRuntimeCreate: true, … }

OVERLAY_ALLOWED_TYPES(metadata-protocol/src/protocol.ts:6860)照此派生,于是 saveMetaItem({ type:'flow', organizationId }) 放行。仓内已有的 packages/objectql/src/overlay-precedence.test.ts:18-22 把这次反转当既成事实记了下来:

Note: object, field, flow, workflow, agent, permission, role, and profile all flipped to allowOrgOverride: true in commit ba252da (feat: add project mode, metadata forms, and org overlays). The invariant now pins the execution/wiring-layer types that MUST stay false.

为什么是缺陷而不是文档陈旧

Prime Directive #13:「An accepted ADR binds until a superseding ADR says otherwise. Reversing a recorded decision is itself a decision: it needs a new ADR(或旧 ADR 上的修订状态行),not a changeset that quietly does the opposite.」这次反转把 ADR 表里点名为 ❌ 的 8 个类型(object/field/flow/workflow/agent/permission/role/profile,含 ADR 明确写「Authorization correctness; overlays would create silent privilege drift」的三个安全类型)一次性翻成 ✅,ADR 正文与状态行都没动。今天读 ADR-0005 的人得到的是与运行时相反的答案,而 ADR 自称是权威。

已经付出的代价

建议(不越权,只列选项)

  1. 修 ADR:给 ADR-0005 加修订段,逐类型说明为什么这 8 类可以 per-org override,以及 ADR 原文点名的三条风险(物理表 schema 分叉 / 执行副作用 / 权限静默漂移)各自如何被消解;这也是 Schedule 触发器不携带任何 org 上下文——定时 runAs:'system' 清扫在多组织部署里仍会产出 organization_id=NULL 的行(#5494 的用户缺席半边) #6155 主路径成立所需要的那段论证。
  2. 修代码:把 ADR 表判 ❌ 的类型改回 allowOrgOverride: false(需评估已在库的 overlay 行的迁移)。
  3. 无论走哪条,给 DEFAULT_METADATA_TYPE_REGISTRY 的相关条目留下 ADR id(Prime Directive [WIP] Add Chinese version of the documentation #13 的推论:实现某个 ADR 决定时把 id 留在代码里),并考虑登记进 scripts/adr-anchors.json——本次反转之所以能悄悄发生,正是因为被编辑的那一行没有点名管辖它的 ADR。

Refs:#6155、#6190、ADR-0005、packages/objectql/src/overlay-precedence.test.ts:18、commit ba252da0b。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: needs-user-decision + domain:spec + target:v17.

    Classification

    Not a defect card — it asks whether an accepted ADR's recorded decision stands or is superseded. Reversing (or ratifying the reversal of) ADR-0005's tenant-customizable whitelist is a public-contract call on the maintainer's desk, not something a dev agent may guess. Both routes the body lists are legitimate and they are mutually exclusive, so no dispatch is possible until the direction is picked. Filed to the decision inbox unchanged.

    Routing rationale (landing site, not title vocabulary)

    domain:spec. The authoritative flag is MetadataTypeRegistryEntry.allowOrgOverride in packages/spec/src/kernel/metadata-plugin.zod.ts (schema at :216, DEFAULT_METADATA_TYPE_REGISTRY entries at :604-:778) — verified on origin/main. Every listed route lands there: route 2 edits the flags directly, and route 3 ("whichever route is chosen") stamps the ADR id onto those same entries. packages/metadata-protocol/src/protocol.ts:6860 only derives OVERLAY_ALLOWED_TYPES from the flag, so it is a consumer, not the fix site. Per the skill's "shared contract surfaces have one owner", anything touching packages/spec belongs to the spec seat regardless of who needs it.

    Premise check — the drift is real, but the affected set is wrong in both directions

    The body's claimed set ("8 types … object/field/flow/workflow/agent/permission/role/profile") is quoted from packages/objectql/src/overlay-precedence.test.ts:19, which describes the state at commit ba252da0b. That note has since gone stale. Measured against origin/main today, entry by entry:

    ADR-0005 says live allowOrgOverride verdict
    object, field ❌ false, false no drift — flipped back since ba252da0b
    hook ❌ false no drift
    datasource ❌ false no drift
    agent ❌ false no drift — contra the body
    flow ❌ true DRIFT (:649)
    permission ❌ true DRIFT (:777) — the one security type still drifted
    tool, skill ❌ true, true DRIFT — not named in the body
    page, app, action ❌ **true**×3 DRIFT — not named in the body
    view, dashboard, report, email_template, translation ✅ true×5 no drift

    Two corrections that change the card's scope before anyone prices it:

    1. The live drift set is 7 types — flow, permission, tool, skill, page, app, action — not the 8 the body names. Three of the body's eight (object, field, agent) are already back to false; five of the live seven (tool, skill, page, app, action) are not in the body at all. The security exposure the body leads with ("three security types") is in fact one: permission. role and profile are not registry types (see 2).
    2. A second, independent staleness in the ADR itself: validation, workflow, approval, profile, role appear in the ADR-0005 table but do not exist in DEFAULT_METADATA_TYPE_REGISTRY at all (full live type list: action, agent, api, app, book, dashboard, dataset, datasource, doc, email_template, external_catalog, field, flow, hook, job, mapping, object, page, permission, position, report, seed, skill, tool, translation, view). So the table names five types that were retired or never landed — whichever route is chosen, the amendment has to reconcile those rows too, not just the flags.
    3. Also unlisted by the ADR while carrying allowOrgOverride: true: dataset, book, position. New types default to false per the ADR's closing rule, so these three are additions the table never recorded.

    None of this changes the body's core claim — the reversal did happen without an ADR, Prime Directive #13 applies — but it shrinks the security surface, moves five types onto the list, and adds a table-vs-registry membership gap the body does not mention.

    Dedup

    Full-text scan of all open issues and PRs across the three repos for allowOrgOverride / ADR-0005 / OVERLAY_ALLOWED_TYPES / ba252da0b: no existing dispatch entry for this drift. Nearest neighbours are context, not duplicates — #6155 (the ruling whose main path rests on this card's outcome), #6190 (the concrete defect this gates), #5992 (ADR numbering ambiguity, a different governance defect). Cross-repo: nothing in objectui/cloud.

    Release board

    target:v17 on criterion ②: a published document declares seven types cannot be per-org overridden while the runtime accepts exactly those writes — declared ≠ enforced on a contract we ship. It also gates #6190, itself a v17 board item; a blocked blocker with no visible gate makes the board lie.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 7, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling — 2026-08-07. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「你帮我综合评估,我接受你的建议」). Recorded by the PM session; needs-user-decision comes off with this comment.

    Decision: split the eight flipped types into two halves.

    1. Revert to the ADR table — allowOrgOverride: false — for the six types with no argued case: workflow, approval (automation row; flow is already ruled back to false via Schedule 触发器不携带任何 org 上下文——定时 runAs:'system' 清扫在多组织部署里仍会产出 organization_id=NULL 的行(#5494 的用户缺席半边) #6155 Q1=B, transfer card [转移·spec] flow 的 allowOrgOverride:true 回滚为 false——#6155 Q1=B 裁决的契约半边,同步了结 #6191 #6283) and permission, role, profile (the trio the ADR itself flags as "Authorization correctness; overlays would create silent privilege drift"). Include a migration assessment for any org-scoped overlay rows already stored under these types.
    2. object / field keep true — the flip is the substance of project mode, i.e. real shipped pull — but the reversal must be legitimized by an ADR-0005 revision section that addresses the ADR's three named risks (physical schema divergence / execution side-effects / privilege drift) specifically for these two types. Until that section merges, the ADR remains the stated authority.

    Both halves: leave the governing ADR id on the touched DEFAULT_METADATA_TYPE_REGISTRY lines and register them in scripts/adr-anchors.json — the reason this flip happened silently is that the edited lines named no ADR (PD #13 corollary).

    Rationale: PD #13 — reversing a recorded decision is itself a decision needing an ADR; the split follows the evidence — object/field have a shipped feature behind them, the other six never received an argument.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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