Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

ADR-0036「Server enforcement」段的「fails to evaluate ⇒ fail-open」写成无条件成立 —— 与 #3828 同源,objectstack#4889 之后 readonlyWhen 未绑定根一类是 fail-CLOSED #3888

Closed

Description

@yinlianghui

越界发现,来自 #3828 的实现(PR #3887)。按 PD #10 立案,未认领、未定级。观察类:同样是一段文档,今天没有用户会撞到它,但它对姊妹系统的失败方向做了一个已经不成立的断言 —— 而且这次是在本仓的 ADR 里,即这套设计的权威记录处。

事实

docs/adr/0036-field-conditional-rules.md 的 ## Server enforcement (framework) 段(:55 起)在逐条讲完 requiredWhen / readonlyWhen 的服务端行为后,:67-68 写:

  • A predicate that fails to evaluate is fail-open and logged (a broken rule must never block a legitimate write).

这一条无条件覆盖上面两个 bullet(requiredWhen 与 readonlyWhen),而自 objectstack#4889 起它对 readonlyWhen 的一个 fault 类不成立:谓词因为引用了本次写入没有绑定的 scope 根(parent.status == 'paid' 而手里没有 master-detail 头)而 fault 时,服务端是 fail-CLOSED。

objectstack origin/main @ fec784863,packages/objectql/src/validation/rule-validator.ts:

事实 位置
未绑定根 → 记日志 treating the field as LOCKED :599-603
同一分支 return true(= LOCKED) :604
其他 fault 才是 failed to evaluate — change allowed through + return false :606-607
stripReadonlyWhenFields 随即 delete 掉该 key :522 / :537
设计说明「readonlyWhen: the UNBOUND-ROOT case is fail-CLOSED (#4889)」,并明确「Every OTHER readonlyWhen fault … keeps the fail-open policy」 :99-109

requiredWhen 那一半仍准确(objectstack#4977 绑了 parent scope 但刻意保留 fail-open 语义,:117-141、:1416-1430),所以要收窄的只是 :67-68 这条概括,不是整段。

与 #3828 的关系,以及为什么另立一单

#3828 / PR #3887 修的是 packages/core/src/evaluator/fieldRules.ts 的模块头,文件面被 issue 明确限定为那一个 docblock,所以本条没有捎带修。两者是同一个陈旧断言的两个副本:代码注释那一份已收窄,ADR 这一份仍在,而 ADR 是新读者查「服务端到底怎么办」时的第一站,留着它等于把刚拆掉的错误心智模型原样保存在更权威的位置。

顺带记录一处已核准确、不需要动的地方,免得后来者重复排查:packages/types/src/form.ts:1076-1085 关于 previousValues 的注释把这条链路讲对了 —— 「faulting on an unbound root and failing OPEN — which let a user edit a locked field and have the change silently dropped on save」,正是本条要补上的那个后果。

建议修法(留给分诊,不自选)

  1. 把 :67-68 收窄为「多数 fault fail-open;readonlyWhen 的未绑定根一类自 objectstack#4889 起 fail-CLOSED(锁死并丢弃写入),按 ADR-0057 D10 以服务端为准」,并在 ## Client enforcement (objectui) 段点一句两端在这一格方向相反(PR docs(core): fieldRules 模块头收窄 readonly fail-open 的「与服务端一致」断言 (#3828) #3887 的 docblock 已是现成措辞,可直接对齐,注意 framework 的 ADR-0057 与本仓 docs/adr/0057-console-ai-chat-one-conversation-docked.md 撞号,引用需写明是 framework 编号);
  2. 只在 :67 加「except an unbound scope root for readonlyWhen(objectstack#4889)」一句,不展开;
  3. 不动,记录为已知边界。

倾向 1:这段的价值就是给出服务端行为的权威概括,而概括错了方向比缺失更贵 —— #3828 的成本正是如此。

查重

仓内三次检索:ADR-0036 fail-open(0 命中)、readonlyWhen 4889(仅 #3828 本身)、0036-field-conditional-rules(0 命中);另检 fail-CLOSED 全文(#3792 / #3368 / #3871 主题不同),无同题单。

Refs: #3828、PR #3887、objectstack#4889、objectstack#4977、ADR-0057 D10(framework 编号)。

Activity

  1. claude commented on Aug 9, 2026

    @claude
    Contributor

    Findings round — promoted: finding → pm:queue.

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


    Generated by Claude Code

  2. self-assigned this
    on Aug 17, 2026
  3. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    认领(objectui 车道分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt,批次 19):branch claude/issue-3888-adr36-failopen-sentence。按晋级评论方向 1 实施:ADR-0036 Server enforcement 段的无条件「fails to evaluate ⇒ fail-open」句收窄(readonlyWhen 未绑定根一类是 fail-CLOSED,引 objectstack#4889),并注记 ADR-0057 编号冲突;#3828 的代码注释副本已由 PR #3887 修正,本卡收掉最后一份未改副本。


    Generated by Claude Code

  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    实施完成,草稿 PR #4952(会话 session_01GTRjn8xBqp75dk7kFupVRt,批次 19,branch claude/issue-3888-adr36-failopen-sentence)。

    premise 成立。:67-68 那句仍在、仍是无条件写法。framework 行号按硬规重新定位 —— 卡片正文按 fec784863 写的 :599-603/:604/:606-607 与晋级评论按 86e6f6c 写的 :601 vs :606 都已漂移;objectstack origin/main @ 23abe2782 的 packages/objectql/src/validation/rule-validator.ts 上是:未绑定根记 treating the field as LOCKED 在 :607-611、return true(LOCKED)在 :612;其他 fault 记 failed to evaluate — change allowed through 在 :614、return false 在 :615;stripReadonlyWhenFields :530/delete :545,批量孪生 :749/:780;requiredWhen fault 仍 fail-OPEN(记日志后 continue)在 :1695-1712。方向断言不变,仅位置更新。

    按方向 1 实施:概括 bullet 带上例外并指向新增的 ### The one fail-CLOSED fault (objectstack#4889) 小节;## Client enforcement (objectui) 段的「the same posture as the server」同样收窄(它断言的是同一件事),并点明两端在这一格方向相反、附静默症状与排障方向,措辞与 PR #3887 的 docblock 对齐。requiredWhen(objectstack#4977)与 visibleWhen 复核仍准确,改为显式的非例外。

    ADR-0057 编号有两层,我只修了一层。 本仓撞号已按卡片处理(写明是 framework 编号,并指出本仓 0057-console-ai-chat-one-conversation-docked.md 无关)。但顺手核验发现 framework 侧那个 D 锚点本身不可解:framework 的两份 ADR-0057 里都没有内容对得上的 D10(ERP authorization 那份 :422 的 D10 是 "Setup-nav surfacing follows the capability";system-data-lifecycle 那份没有任何 D 编号决策,用 P0–P4),而 20 个 framework 文件 + 一条 adr-anchors/ invariant 骑在这个锚点上。规则本身是真的、代码自证,不成立的只是锚点 —— 所以本 PR 把引用写成归属式(「framework 的 rule-validator / lint 诊断 / QA runner 都这么引」)而非断言锚点可解:本卡的要点就是不要在权威记录里保存查不实的断言,在同一次改动里新种一个会很讽刺。已另立 objectstack#9255(观察类,finding,未认领未定级),本 PR 不修。

    验证(文档类正向判据):control-bytes 门 + 门外自扫、check-doc-links(13 scan roots 全绿,docs/** 在其扫描面内)、check-doc-component-types、两个 changeset 门,仓根 turbo run type-check --concurrency=2 在 flock 下 81/81。零代码改动,故无新增测试 —— 每条新声称配一个 framework file:line 指针作为替代判据,明细在 PR 正文。


    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

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions