Repository navigation
objectql: evaluateOptionVisibility continues on a predicate fault, so a select option's server-side gate admits the write — fail open or fail closed, under ADR-0089 (the runtime half of #22394) #22402
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, search
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsDecision record: the server option gate's fault direction ·
domain:engine · seat 2·session_01Bw3y2DWhT9RPnrmDsNqEVG· 2026-10-09T04:12Z. ⛔ Not a claim. In the same act this card moves frompm:queuetoneeds-user-decision.维护者速读
- 一句话问题:下拉选项上「谁能选它」的规则自身写坏了(引用了不存在的字段或成员),用户仍然提交了这个选项。今天服务端放行,只记一条 warn。要不要改成拒绝这次保存?
- 为什么到你这里:本卡分诊读法是「拒绝」(ADR-0137 D2:提交时响亮拒绝)。但在同一问题上,[#18682 v1 切出] UI 谓词三缝(visibleWhen / readonlyWhen / requiredWhen)在关联字段不可读时 fail-open —— 父卡裁定的「不可读即响亮报错、⛔ 绝不静默为真」在这三缝上今天做不到 #19727 的分诊答复
5788580082读的是「option 可见性不是 field-rule 谓词,ADR 保持 fail-open」。落地 D2 的 PR fix(objectql)!: a field-level requiredWhen / readonlyWhen that cannot be evaluated refuses the write (ADR-0137 D2) #20028 据此把 option 显式排除,并钉了对照 pin。两份席位读法相反,而 ADR-0137 的 D2–D4 正文都没有点名 option 的写路径。本卡正文写明:座位读法不一致就转决策卡,⛔ 不施工。 - 为什么不走代裁:这是一个鉴权门(例如「仅管理员可选」的选项)在故障时的方向,属安全/权限边界,在人工地板。
- 席位推荐:A(写路径拒绝;系统写无 acting user 的那一臂照旧放行)。
- 你要做的:回一个字母,A 或 B。
背景(读数取自
origin/main83e7ae93ad)packages/objectql/src/validation/rule-validator.ts的evaluateOptionVisibility:谓词求值失败时分两臂。no-acting-user:系统写,且谓词读current_user。predicate-fault:其余一切故障,鉴权调用方的也在内。- 两臂都是
warn之后continue; // fail-open,写入放行。
- 同文件头注释原文:「What D2 does NOT reach: option
visibleWhen({@link evaluateOptionVisibility}) — D2 names a FIELD-rule predicate, an option's visibility is not one, and it stays fail-open」。 - 现行 pin,两条:
rule-validator.option-visibility.test.ts:「authenticated caller + a genuinely faulting predicate ⇒ the loud warn, reason predicate-fault, value admitted」。engine-option-permission-predicate.test.ts:「NO resolver ⇒ no permission data: loudly unevaluable and ADMITTED, never a silent denial」。- PR fix(objectql)!: a field-level requiredWhen / readonlyWhen that cannot be evaluated refuses the write (ADR-0137 D2) #20028 的消融腿 A6(把 option 故障臂改成报错)判红。也就是说,fail-open 是被刻意钉住的。
- 实际使用:
examples/app-showcase/src/data/objects/cascading-select.object.ts有 5 个选项级visibleWhen,其中 4 个是国家→省份级联,1 个是'org_admin' in current_user.positions角色门。该对象的描述写明「enforced client-side (offered set) AND server-side」。读数:git grep -n "visibleWhen" origin/main -- examples/app-showcase/src/data/objects/cascading-select.object.ts,选项行 5 条;对照词options:在同文件必中。
Governing text
- ADR-0137 D2:「At submit time, a field-rule predicate that cannot be evaluated refuses the write and names the field and the rule.」
- ADR-0137 D3:「A faulting
visibleWhenshows the field.」它的理由:「the submit-time refusal is what keeps fail-open from being a silent permission grant. Neither is safe alone.」 - ADR-0137 D4:「a gate predicate that is blank or faulting is diagnosed」。
- ADR-0124 D1:「The server is the enforcement point; client-side gating is a usability courtesy」。
- 检索:
git grep -n -i "option" origin/main -- docs/adr/0137-predicate-fault-semantics-are-contract.md只有 1 处实义命中(第 89 行,D1 人口普查的引文「field / option / grid-column …」),D2–D4 零命中;对照词submit在同文件 9 命中。 - 协议声明:两个选项都不改协议,都是在 ADR-0137 之内定范围。
前提(复升级时逐条重跑)
- 故障臂仍放行:
git grep -n "continue; // fail-open" origin/main -- packages/objectql/src/validation/rule-validator.ts(阳性对照:evaluateOptionVisibility在同文件必中)。 - ADR-0137 的 D2–D4 未点名 option:上面那条 grep。
- 没有维护者裁决覆盖 option 写路径的方向:见下方
Prior rulings read行。
选项 × 真实代价
选项 做什么 客户可感知的后果 A 写路径拒绝 鉴权调用方提交了一个「规则本身坏掉」的门控选项:返回 400,沿用字段规则同一拒绝信封( unevaluable),点名选项、字段与故障。系统写(seed 等,无 acting user,且谓词读current_user)那一臂照旧放行规则写坏的应用,选到该选项的保存会失败,并看到原因。今天被静默绕过的角色门(如「仅管理员」选项)从此真正生效 B 维持现状(诊断后放行) 不改代码,依靠 #22394 的构建期拦截覆盖能静态判定的形状 规则写坏时,任何人都能保存被隐藏的选项值,只有服务器日志里一条 warn。构建期判不了的形状(计算键、计算接收者、存量行、 OS_ALLOW_UNLINTED_METADATA_WRITES=1下保存的元数据)永远不会被拦业务含义直译
- A:门卫拿不准就不放人,并告诉你为什么。好比审批规则出错时,交易挂起而不是直接通过。
- B:门卫拿不准就放人,只在值班本上记一笔。规则写坏的那一刻,角色门等于没有。
四轴
- ① 项目长远合理性:A 让「服务端是执行点」(ADR-0124 D1)在选项门上也成立,与字段级 D2 用同一信封、同一语义,删掉一个「option 例外」。B 保留这个特例,而 ADR-0137 D3 自己说过,fail-open 只有和提交时拒绝配对才安全。主流平台(如 Salesforce 的 validation rule 与 picklist 依赖)在服务端规则出错时阻止保存,走的是 A 的形态。
- ② 实际业务拉动:showcase 有 5 个选项门(含 1 个角色门),且声明服务端执行。本卡与 lint: a select option's
visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274、lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394 测到的故障形状(No such key: org)来自真实的作者写法错误。拉动真实,但量小。 - ③ 防 AI 犯错:AI 写错一个选项谓词时,A 在第一次保存就响亮拒绝并点名故障;B 静默放行,只有读服务器日志才看得到,正是错误被掩盖的温床。
- ④ 创业阶段不扩散:A 不加新错误码、不加新键,复用已有拒绝信封。B 零改动,但留下一个要永久解释的例外。
os-decision-facets
- ① 项目长远合理性:A 缩小特例(删掉「option 不归 D2」的例外),B 保留它。
- ② 实际业务拉动:showcase 有 5 个选项门、1 个角色门,声明服务端执行;故障形状来自真实作者错误。
- ③ 防 AI 犯错:A 响亮拒绝并点名故障;B 静默放行,只留 warn。
- ④ 创业阶段不扩散:A 零新码零新键;B 零改动但留一个永久例外。
Prior rulings read:
option visibleWhen predicate fault fail-open server write evaluateOptionVisibility ruling→ 9 hits (MCPsearch_issues, repo-scoped, open and closed), threads of #19727, #17778, #22157, #22274 and PR #20028 read → 2 seat readings, 0 maintainer rulings; ADR-0137 D2/D3/D4, ADR-0124 D1; thread: 5788580082.推荐:A。 只看①选 A;②③④ 是否翻转:否。回退:B。
置信缺口:看不见今天的部署里有多少选项谓词正在故障。另一个盲点是级联谓词(
record.country == 'cn'):当country这一列没有被回填进合并记录时,它是否会故障?若会,A 会把今天能保存的级联写法变成拒绝。两件都留给 dev 先测,命中则先修生产者。裁后执行
- A:本卡回
pm:queue,由本席派发。- dev 先测人口:示例应用与 dogfood 语料里今天会故障的选项谓词。
- 再把
predicate-fault臂改为拒绝,复用unevaluableRuleError信封,⛔ 不加新码;no-acting-user臂不动。 - 上面两条 pin 翻转为拒绝 pin,头注释改为与 D2 一致。
- changeset:
@objectstack/objectql,Clause-②: no (narrowing),BREAKING,迁移句是「修正谓词」。入队前过一次契约复审档复核。
- B:本卡关 not planned,回链本评论;代码与 pin 不动。
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsRuling: batch #299 item 2 · letter A · maintainer 「299 同意」 2026-10-09T05:22Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Presented in batch #299 from thedomain:engineseat 2's decision record (6074111128) on the card the triage seat split from #22394: A the server option gate fails closed on the write path for an authenticated caller (thepredicate-faultarm refuses with the existing field-ruleunevaluableenvelope, naming the option, the field and the fault; theno-acting-userarm unchanged); B keep fail-open and rely on #22394's build-time verdicts. The seat recommended A, and so did this seat; the maintainer answered 「299 同意」. Thread-read: 6074111128. Freshness: body unchanged; no comment since the presentation; labelsbug,priority:p2,needs-user-decision,domain:engine,area:records. Premises re-read onorigin/mainca135dcc40:objectql/src/validation/rule-validator.ts:2996(no-acting-user),:3003(predicate-fault),:3006(continue; // fail-open), and the header note at:184("What D2 does NOT reach: optionvisibleWhen"); ADR-0137 D2 (:134, a faulting field-rule predicate refuses the submit), D3 (:149, render stays fail-open), D4 (:162, a blank or faulting gate predicate is diagnosed), none naming the option gate on the write path; the two pinsrule-validator.option-visibility.test.tsandengine-option-permission-predicate.test.tsexist;examples/app-showcase/.../cascading-select.object.tscarries the option-level predicates the record counts.The ruling
A — the server option gate fails closed on the write path. For an authenticated caller, an option
visibleWhenthat faults while the write is judged refuses the write with the field-ruleunevaluableenvelope already in use, naming the option, the field and the fault; no new refusal code. Theno-acting-userarm (a system write with no acting user whose predicate readscurrent_user) stays admitted and loud. The reading of ADR-0137 is settled for this seam: D2's submit-time refusal reaches the option gate on the write path, because that gate is the server's enforcement of who may pick the option (ADR-0124 D1), and D3's render fail-open is unchanged; the earlier seat reading that an option's visibility is not a field rule (#19727 triage 5788580082; PR #20028's exclusion, its header note and its two pins) is superseded on the write path. The two pins flip to refusal pins; the header note at:184is rewritten to match D2. Build order: the dev measures the population first (option predicates that fault today in the example applications and the dogfood corpus, and whether a cascading predicate such asrecord.country == 'cn'faults when the column is absent from the merged record) and fixes the producers before flipping the arm;Clause-②: no (narrowing), BREAKING on@objectstack/objectql, the migration sentence "fix the predicate, whichos buildnames for every statically judgeable shape"; contract review before the queue. ⛔ Not taken: B (a role gate whose rule is broken is no gate, with one warn line as the only trace; the shapesos buildcannot judge stay admitted for good).Prior rulings read: ADR-0137 D2 / D3 / D4; ADR-0124 D1 (the server is the enforcement point); ADR-0089 as amended by #17778 (objectui#8069 A); ADR-0056 D2 (close the anonymous fail-open hole); ADR-0049; #19727 triage 5788580082 (a seat reading, superseded here on the write path); PR #20028; #22274 / #22394 (the build half, independent).
check-prior-rulingsover 8 terms → 14 ADR hits (ADR-0137 D3, ADR-0048 D5, ADR-0056 D2 / D3 / D10, ADR-0057 D7, ADR-0070 D1, ADR-0089 §1, ADR-0091 D6, ADR-0106 D1 among them), none ruling the option gate's write-path direction; thread: 0 rulings of 1 comment. 自检: 只看①选 A;②③④ 是否翻转:否。置信缺口:现网故障中的选项谓词数量看不见;级联谓词在合并记录缺列时是否故障未测(命中则先修生产者,再翻臂)。State
needs-user-decision→pm:queue(domain:engine,priority:p2,area:records) in this act; theRuled:line added to the body. The engine seat dispatches: population measurement first, then the arm, the pins, the header note, the changeset, the contract review.
Generated by Claude Code
- added and removed
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 · 2026-10-09T06:13Z
Session:session_01Bw3y2DWhT9RPnrmDsNqEVG
Account:os-tesla(the seat's linked user, asget_meanswers it; the card's assignee)
Branch:claude/issue-22402-option-gate-fails-closed
Worktree:objectstack-issue-22402
Domain:domain:engine
Seat:domain:engine#2(seat post #20966)
Ruling-ref: 6074855432 (batch #299 item 2, letter A; fetched this round)
File surface (read onorigin/main86ae119920):packages/objectql/src/validation/rule-validator.ts:evaluateOptionVisibility'spredicate-faultarm refuses; theno-acting-userarm is unchanged; the header note that says D2 does not reach optionvisibleWhenis rewritten.packages/objectql/src/validation/rule-validator.option-visibility.test.tsandpackages/objectql/src/engine-option-permission-predicate.test.ts: the two fail-open pins flip to refusal pins, plus the ruling's new pins..changeset/22402-option-gate-fails-closed.md(@objectstack/objectql, BREAKING).- Published text this change makes false, cross-lane, prose only:
packages/lint/src/validate-expressions.ts: theoption visibleWhenconsequence sentence ("The server evaluates an option predicate fail-OPEN …") and its two doc comments (domain:devx);packages/spec/src/data/field.zod.ts: the optionvisibleWhenJSDoc sentence "the server — fail-open for an option — admits the value unchecked" (domain:spec).
- Conditional, declared before editing: a producer of an option predicate that the population census finds faulting (the showcase's
examples/app-showcase/src/data/objects/cascading-select.object.tsis the known carrier). - Stop on a breach and explain in the report.
Container & model:M,mode:subagent,model: default(dispatch-gates --tierat86ae119920: no path-derived mandate; a ruling-implementation card with a measurement step)
Clause-②: no (narrowing)
Responsibility:objectql's server option gate admits a write whose option predicate faults | the build-time verdicts (lint: a select option'svisibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274, lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394) cover statically judgeable shapes only; nothing covers computed keys, computed receivers, stored rows orOS_ALLOW_UNLINTED_METADATA_WRITES=1saves | any authenticated caller who submits a gated option value whose predicate faults; the showcase carries 5 option gates, one of them a role gate
Thread-read: 6074855432
Serial constraints cleared: at 2026-10-09T06:13Z, 18 open PRs read byfilename; none touchespackages/objectql/src/validation/, the two pin files,validate-expressions.ts,field.zod.tsorcascading-select.object.ts. The same-axis (area:records) in-flight card objectql: insert shows beforeInsert hooks the caller's readonly keys, then strips them — the insert-side twin of #16344 #22306 (PR fix(objectql): withhold caller-supplied readonly values from beforeInsert (#22306) #22413) editspackages/objectql/src/engine.ts: different files. lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394 (the build half,domain:spec, unclaimed) is independent by the ruling. Neither pins the option gate's fault direction.
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsCross-lane note from
domain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T06:17Z. ⛔ Not a claim, ⛔ not a request to change this card's scope. A reply is owed only on an objection.The build half of this card's family, #22394, is ready to land as PR #22427 (at-tier contract review PASS
6075493996). Its review escalated two items whose landing is this card's area, recorded here so they ride with the runtime half:- A mirror constant with no cross-package pin.
packages/lint/src/validate-expressions.tsOPTION_CHECK_ACTING_USERhand-mirrors the key set ofbuildEvalUser's return inpackages/objectql/src/engine.ts({ id, positions, organizationId }), because@objectstack/lintcannot import ObjectQL. If a key is ADDED tobuildEvalUser, lint over-refuses (fail-closed, safe). If a key is REMOVED, lint keeps admitting a member the option check no longer binds, and that member faults open again with no failing test. The review asks for a key-set pin onbuildEvalUser's return, on the ObjectQL side, that names lint's constant. lint: a select option'svisibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274'sOPTION_VISIBLE_WHEN_BOUND_MEMBERShas the same shape and no such pin either. - A read below a bound member is not judged.
current_user.positions.xorctx.user.organizationId.y: the first member is bound, so the build passes it, and the option check faults open on it. This is the same fault-open this card asks about (computed keys, computed receivers), at one more hop.
Also from #22394's report: the stale
EvaluateRulesOptions.currentUsertype inrule-validator.ts(roles?) does not feed lint's allowlist, so #22394 leaves it to this card.- A mirror constant with no cross-package pin.
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22402,
"status": "done",
"branch": "claude/issue-22402-option-gate-fails-closed",
"pr": "#22436",
"session": "session_01Bw3y2DWhT9RPnrmDsNqEVG — shared with the PM (mode:subagent); this run is identified by the branch",
"premise_still_valid": true,
"summary": "Ruling A is implemented. The predicate-fault arm of evaluateOptionVisibility now refuses the write through the existing field-rule unevaluable envelope: VALIDATION_FAILED, rule_violation, constraint.reason 'unevaluable', constraint.rule 'visibleWhen', constraint.fault, and the option as value. The message names the option, the field and the fault, and no new code is added. The no-acting-user arm still admits. A system write whose predicate faults without reading the acting user is refused too, mirroring the field-level D2 precedent: requiredWhen binds no user and refuses every fault, and the readonlyWhen strip refuses isSystem writes. The population census found 5 option predicates (all on showcase_cascade), and none faults on any write path; a cascade with its column absent reads null == 'cn', a clean false over the total record, so no producer needed fixing. Same-semantic pins and published text are swept repo-wide in one round: the lint consequence sentence, the spec JSDoc and describe with regenerated references, and the dogfood ledger row, which moves to fail-closed. The PM's mid-flight items are done: the below-bound-member pin is added (measured: No such key, refused); the stale currentUser roles? type and the OPTION_CHECK_ACTING_USER drift are in Acceptance notes with carrier none.",
"tests": "Head 0da1d65. (1) objectql: full vitest --project local at 95bdaf3 gave 388/389 files, 7681/7682 tests. The one red was federated-injected-column-readers.test.ts, whose READERS row named the moved seam (referenceTargetOf now in referenceReadThrough); it was fixed in 41c797b and re-run green, 4 files 89/89. objectql non-test src is byte-identical 95bdaf3 to head (git diff of the non-test sources: 0 bytes). objectql typecheck (tsc + check:test-typecheck) exit 0. (2) lint: full vitest 128 files, 5881 tests, plus typecheck, exit 0 at 5bee38e (lint unchanged since). (3) spec: vitest --project local 627 files, 18757 passed and 1 todo; typecheck exit 0; check:generated 15/15 after --fix regenerated check:docs (3 reference pages). (4) dogfood test/expression-conformance.test.ts 7/7. The first attempt hit an unresolved @objectstack/verify, so it is NOT MEASURED; it was built and re-run. (5) cli test/validate-field-predicate-traversal.test.ts: integration tier with a comment-only edit, declared to CI, NOT MEASURED locally. (6) Ablation, fix committed first. The tests import ./rule-validator.js and ./engine.js relatively, so they read src and no dist rebuild applies. node scripts/ablation-replace.mjs used anchor 'errors.push(refusal);' with replacement 'void refusal;': anchor 1 to 0, blob a08b8561dd03 to ea63d5eaec09. Baseline 87/87; mutated 20 failed and 67 passed, every new or flipped refusal pin red and every control green. Restore: blob == HEAD a08b8561dd03 and git diff HEAD empty. (7) Gates: dispatch-gates derived 115 families at 0da1d65 and all 115 exit 0; --ran with exit codes reports '115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN'. Four of them (spec check:api-surface, check:skill-examples, check:dts-closure, check:dual-build-cjs-loads) first exited 3/1 because a stale build I had started was rebuilding dist under them; that is a prerequisite miss, not a measurement. I rebuilt client-react and organizations and re-ran all four green.",
"mcp_calls": "0",
"api_writes": "3 relay writes, each a POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving PR #22436 as a draft, run 37899188115, body read back byte-identical; (2) label-write --assign os-tesla, POST /repos//issues/22436/assignees, run 37899250290, read back; (3) this os-dev-report comment, POST /repos//issues/22402/comments. Separately, git push of the branch (not REST, 7 pushes). No labels were written: the dispatch named none and skip-changeset does not apply.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer — packages/core/src/utils/import-runner.ts previewVerdict(data, 'update', rowCtx), which goes to validateData and then ObjectQL.validate (an import dry run of a matched row) · evidence: on origin/main 3054516, built dist: engine.validate('showcase_cascade', { province: 'zj' }, { mode: 'update' }) and validate(..., { note: 'x' }, { mode: 'update' }) both refuse note rule_violation unevaluable. That is a requiredWhen reading the omitted country, with 'which this object does not declare' although country is declared. The real by-id update { province: 'zj' } over a stored cn row admits. An update-mode preview reads no stored row, so the merged record is not total. Since this PR the option gate joins that class: the preview refuses the province pick as unevaluable, where before it was a false all-clear. · dedupe words: validate update mode preview no prior row · import dry run requiredWhen unevaluable omitted column · preview refuses row the write admits",
"carrier: the release-notes compilation at the next release (maintainer) · the pending changesets .changeset/22032-object-save-door-option-visible-when.md, 22157-option-visible-when-parent.md and 22274-option-visible-when-members.md say a faulting option predicate is logged and admitted. Each records its own landing, and editing them would make them false for a release cut before this PR lands, so this PR's changeset carries one supersession sentence instead · noted, not filed"
],
"premise_checks": "Zone 2, measured. (1) The two named pins are confirmed. The repo-wide sweep found more same-semantic pins, all flipped: engine-field-predicate-fault.test.ts block (d), the former 'option visibleWhen stays fail-open' control, now the real-engine pin; the system-write no-user-root pin and the NO permission data pin in rule-validator.option-visibility.test.ts; lint's consequence 'fail-OPEN' pin; and the dogfood ledger row cel-select-option-visible. (2) The whole predicate-fault arm refuses, settled from precedent, not narrowed. (3) The envelope names the option (value), the field (field) and the fault (constraint.fault and the message) with no new code. (4) Call sites: one caller, evaluateValidationRules. In engine.ts that is insert single and batch (:13904), validate() (:13089), by-id update (:15500), bulk per-row (:15803), and bulk with no prior row (:15819), which is unreachable for an object with an option gate because fieldsNeedPrior counts it. The import carve-out is now ExecutionContext.keptOptionValues (execution-context.zod.ts:466, formerly admittedUnmatchedOptions); pickedGatedOptions skips a value that matches no option, so it is unaffected. (5) Census: 5 option predicates in examples and dogfood, all on showcase_cascade; 0 fault on a write path; no seed or dogfood write picks a gated option.",
"pm_mid_flight_inputs": "Note 6075512559. Item 1: pin added. current_user.positions.x and ctx.user.organizationId.y both measured No such key, refused; has() over the same hop is a clean false (invalid_option). Item 2: the stale EvaluateRulesOptions.currentUser type (rule-validator.ts:396, roles?) is left in Acceptance notes. Bounded-in-place condition 1 fails: it is type drift on the exported EvaluateRulesOptions, a different class from this card. Item 3: OPTION_CHECK_ACTING_USER (PR #22427, not on origin/main e02833c) is not built. The Acceptance note says a removed buildEvalUser key now refuses at write instead of faulting open; carrier none.",
"deviations": [
"Files beyond the claim's declared surface, each declared in the PR body. engine-field-predicate-fault.test.ts, lint validate-expressions.test.ts and the dogfood expression-conformance.ledger.ts are covered by the pin-sweep rule. content/docs/references/{data/field,data/picklist,ui/view}.mdx were regenerated by spec check:generated --fix. federated-injected-column-readers.test.ts renames one READERS row because the traversal detection moved to a shared referenceReadThrough. packages/cli/test/validate-field-predicate-traversal.test.ts changes a header comment only.",
"The branch is 3 commits behind origin/main e02833c, and dispatch-gates warned the derivation tree is stale (ci.yml, engine-double-contract.pinned.json, partition-test-shards.mjs changed there). Those commits touch none of this PR's files, so I did not merge; CI's merge ref judges the join.",
"A gate battery run against an earlier head (5bee38e) was abandoned for the head move. Its runner's orphaned child (pnpm check:type-check-debt, a full turbo build) raced the final battery. I killed its process group 16827 (my own) and rebuilt the two packages it left half-built."
],
"files_changed": [
".changeset/22402-option-gate-fails-closed.md",
"content/docs/references/data/field.mdx",
"content/docs/references/data/picklist.mdx",
"content/docs/references/ui/view.mdx",
"packages/cli/test/validate-field-predicate-traversal.test.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/lint/src/validate-expressions.ts",
"packages/objectql/src/engine-field-predicate-fault.test.ts",
"packages/objectql/src/engine-option-permission-predicate.test.ts",
"packages/objectql/src/federated-injected-column-readers.test.ts",
"packages/objectql/src/validation/rule-validator.option-visibility.test.ts",
"packages/objectql/src/validation/rule-validator.ts",
"packages/qa/dogfood/test/expression-conformance.ledger.ts",
"packages/spec/src/data/field.zod.ts"
],
"gates_head": "0da1d65ec",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-doc-frontmatter.mjs :: exit 0",
"node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
"node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
"node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
"node scripts/check-docs-section-name.mjs :: exit 0",
"node scripts/check-docs-section-name.mjs --self-test :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-section-landing-index.mjs :: exit 0",
"node scripts/check-section-landing-index.mjs --self-test :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
"pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
"pnpm --filter @objectstack/spec run check:docs :: exit 0",
"pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
"pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
"pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
"pnpm --filter @objectstack/spec run check:generated :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
"pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cli-test-child-env :: exit 0",
"pnpm check:corpus-claim-drift :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-anchors :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:docs-audit-scope :: exit 0",
"pnpm check:docs-redirects :: exit 0",
"pnpm check:docs-single-h1 :: exit 0",
"pnpm check:docs-spec-enumerations :: exit 0",
"pnpm check:docs-transcript-drift :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:error-code-casing :: exit 0",
"pnpm check:error-status-conformance :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:merge-driver :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:pm-prior-rulings :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:published-readme-links :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:quick-reference-counts :: exit 0",
"pnpm check:react-page-adapter-contract :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:role-word :: exit 0",
"pnpm check:skill-identifier-liveness :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:spec-parsed-alias :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:vendor-version-stamps :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22402,
"round": "patch round 1",
"status": "done",
"branch": "claude/issue-22402-option-gate-fails-closed",
"pr": "#22436",
"head": "2c1c85aff50aa5cb7505061096db75e8c990faf5",
"session": "session_01Bw3y2DWhT9RPnrmDsNqEVG — shared with the PM (mode:subagent); this run is identified by the branch",
"premise_still_valid": true,
"summary": "Patch round 1 answers contract review 6076709701. (1) The blocking ② item: the changeset's '(ADR-0131 D9)' provenance is replaced. It now names the launch-window convention's record (scripts/check-changeset-no-major.mjs) and ADR-0137 D2 as extended to the option gate by the ruling on #22402 (letter A); nothing else in the changeset moved. (2) origin/main 05c7c3f is merged (803e74c, no conflict). In PR #22427's lint changes, only one docblock sentence recorded a now-false admission, and it gains a clarifier. (3) The rule-validator ADR anchor invariant is corrected and ADR-0137 added to its adrs. (4) The checklist cascade item's source line ('fail-open on unevaluable', about the option gate) is corrected, revision 1 → 2 with history. The cel-field-rule ledger comment is left as an Acceptance note: not mechanical, condition ② fails. (5) The lint wording 'every write by an acting user' is kept. The PR body is not edited; the 'Patch round 1' section for the seat to add is in pr_body_patch_round_1_section.",
"tests": "Head 2c1c85a, after pnpm install --frozen-lockfile and a rebuild of the @objectstack/lint... and @objectstack/objectql... closures under the lock (VERDICT command-exit 0, held 779s). spec check:generated: all 15 up to date. lint: full vitest 128 files, 5894 tests, plus typecheck (tsc + check:test-typecheck), exit 0. objectql: full vitest --project local 390 files, 7698 tests, plus typecheck, exit 0. Gates: dispatch-gates derived 125 families on 2c1c85a, 10 more than round 0 because of the merge and the anchor and checklist edits (check:adr-anchors, check:platform-checklist, check-scripts-symbol-anchors and others). All 125 exit 0. check:skill-examples and check:dual-build-cjs-loads first exited 3, a PREREQUISITE miss because the fresh worktree had unbuilt packages, so it was NOT MEASURED. I re-ran both green after the battery's own full turbo build (check:type-check-debt). --ran with exit codes: '125 derived, 125 run, 0 NOT-MEASURED, 0 UNRUN'. No ablation this round: the refusal code is unchanged since 0da1d65 (git diff 803e74c..2c1c85a touches only the changeset, the checklist JSON, one lint docblock and the anchor JSON).",
"mcp_calls": "0",
"api_writes": "1 relay write this round: this os-dev-report comment, a POST /repos//issues/22402/comments via POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]. One git push carried the merge commit 803e74c and the patch commit 2c1c85a (not REST). The PR body was not edited.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat's docs-only follow-up card (contract review ③5) · the dogfood ledger row cel-field-rule comment still says fail-soft-log on all three slots, stale since ADR-0137 D2, not since this PR. Rewriting it means deciding the row's failPolicy, which is a classification judgment, so bounded-in-place condition ② fails · noted, not filed"
],
"review_items": {
"1_changeset_provenance": "fixed: '(ADR-0131 D9)' replaced; nothing else in the changeset moved",
"2_merge_and_22427_text": "merged 05c7c3f as 803e74c. OPTION_CHECK_ACTING_USER docblock: no admission text. Member refusals print FIELD_TRAVERSAL_CONSEQUENCE, which already states the refusal. optionVisibleWhenUserMembers docblock: a clarifier added after its measured-admission sentence. The test comments (validate-expressions.test.ts:2242, protocol.runtime-authoring-gate.test.ts:2485) are past-tense measurements, left as history. The 22394 changeset is left; the supersession sentence covers it",
"3_adr_anchor": "invariant corrected; ADR-0137 added to adrs; check:adr-anchors exit 0",
"4b_checklist": "about the option gate, so fixed: source line corrected, revision 1 → 2, history entry; check:platform-checklist exit 0",
"4c_ledger_cel_field_rule": "left, Acceptance note: condition ② fails (a failPolicy classification judgment, not a mechanical text fix); stale since D2",
"5_lint_wording": "kept 'every write by an acting user': true in every message that carries it; the broader form would need the no-acting-user exception, which applies only to user-reading predicates",
"round0_correction": "the round-0 reason for not editing pending changesets was backwards (an edit lands only with this PR); the choice stands because each entry records its own landing"
},
"pr_body_patch_round_1_section": "## Patch round 1\n\nHead2c1c85aff. Contract review 6076709701 (FAIL on ② only); every ① and ③ judgment there carries over.\n\n- ② fixed. The changeset's parenthetical "(ADR-0131 D9)" had been copied from another changeset and named a decision about organization stamps. It is replaced: theminorbump cites the record of the launch-window convention (scripts/check-changeset-no-major.mjs), and the governing decision is named as ADR-0137 D2, extended to the option gate on the write path by the maintainer's ruling on #22402 (letter A). Nothing else in the changeset moved.\n-origin/mainmerged (05c7c3f, merge commit 803e74c, no conflict). I read PR #22427's lint changes:\n - TheOPTION_CHECK_ACTING_USERdocblock states no admission.\n - Its member refusals printFIELD_TRAVERSAL_CONSEQUENCE['option visibleWhen'], which already states the refusal.\n - One docblock sentence (optionVisibleWhenUserMembers) records an admission measured before that verdict existed. It gains: "(Since #22402 such a fault refuses the write instead, ADR-0137 D2; the build verdict still names it first.)"\n - Its test comment and theprotocol.runtime-authoring-gate.test.tscomments are past-tense measurements, left as history.\n - Its changeset (22394-option-visible-when-evaluser-members.md:27) stays as its own landing's record. The supersession sentence here covers it.\n- ADR anchorscripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json. The invariant no longer says a broken predicate fails open. It now states:\n - D2's refusal forrequiredWhenandreadonlyWhen, and for the option gate, with the no-acting-user case excepted;\n - what stays fail-open: the render side (ADR-0137 D3), a brokenformatorjson_schema, and a rule that throws.\n -ADR-0137is added toadrs.\n- Checklistrecords-forms.cascading-multilevel-and-clear: its source line forevaluateOptionVisibilitysaid "fail-open on unevaluable", which this PR makes false. Corrected, with revision 1 → 2 and a history entry. No step or oracle moves.\n- Dogfood ledgercel-field-rulerow comment: left as it is, as an Acceptance note. It has been stale since D2, not since this PR. Rewriting it honestly means deciding whether that row'sfailPolicystaysfail-soft-logafter D2. That is a classification judgment, not a mechanical text fix, so bounded-in-place condition ② fails. It rides the seat's docs follow-up card.\n- Lint wording (③ ① item 10): kept "every write by an acting user". The sentence is true in every message that carries it (a traversal, an unbound root, an unbound member). The broader statement would have to carry the no-acting-user exception, which applies only to predicates that read the acting user.\n- Correction to the round-0 reason for not editing the pending changesets. That reason ("would be false for a release cut before this lands") was backwards, because an edit lands only with this PR. The choice stands because each entry records its own landing.\n\nVerification on head 2c1c85a, afterpnpm install --frozen-lockfileand a rebuild of the@objectstack/lint...and@objectstack/objectql...closures:\n-spec check:generated: 15/15 up to date.\n- lint: full suite 128 files, 5894 tests, plustypecheck, exit 0.\n- objectql: full--project local390 files, 7698 tests, plustypecheck, exit 0.\n-dispatch-gatesderived 125 families on 2c1c85a, includingcheck:adr-anchors,check:platform-checklistand the changeset gates, and all 125 exit 0.\n -check:skill-examplesandcheck:dual-build-cjs-loadsfirst exited 3, because the fresh worktree had unbuilt packages. That measured nothing. I re-ran both after the battery's own full build: green.\n ---ranwith exit codes: "125 derived, 125 run, 0 NOT-MEASURED, 0 UNRUN".",
"deviations": [
"This round's file surface beyond the round-0 PR: scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json and docs/qa/platform-checklist/areas/records-forms.json, both directed by the review and the PM; plus one docblock in packages/lint/src/validate-expressions.ts that came in with the merge."
],
"files_changed_this_round": [
".changeset/22402-option-gate-fails-closed.md",
"docs/qa/platform-checklist/areas/records-forms.json",
"packages/lint/src/validate-expressions.ts",
"scripts/adr-anchors/packages__objectql__src__validation__rule-validator.ts.json"
],
"gates_head": "2c1c85aff",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-doc-frontmatter.mjs :: exit 0",
"node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
"node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
"node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
"node scripts/check-docs-section-name.mjs :: exit 0",
"node scripts/check-docs-section-name.mjs --self-test :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-scripts-symbol-anchors.mjs :: exit 0",
"node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0",
"node scripts/check-section-landing-index.mjs :: exit 0",
"node scripts/check-section-landing-index.mjs --self-test :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
"pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
"pnpm --filter @objectstack/spec run check:docs :: exit 0",
"pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
"pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
"pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
"pnpm --filter @objectstack/spec run check:generated :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
"pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"pnpm check:adr-anchors :: exit 0",
"pnpm check:agent-test-spelling :: exit 0",
"pnpm check:bash32-floor :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cli-command-ids :: exit 0",
"pnpm check:cli-test-child-env :: exit 0",
"pnpm check:corpus-claim-drift :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-anchors :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:docs-audit-scope :: exit 0",
"pnpm check:docs-redirects :: exit 0",
"pnpm check:docs-single-h1 :: exit 0",
"pnpm check:docs-spec-enumerations :: exit 0",
"pnpm check:docs-transcript-drift :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:entry-guard :: exit 0",
"pnpm check:error-code-casing :: exit 0",
"pnpm check:error-status-conformance :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:merge-driver :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:parse-guard :: exit 0",
"pnpm check:platform-checklist :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:pm-prior-rulings :: exit 0",
"pnpm check:pnpm-filter-targets :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:published-readme-links :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:quick-reference-counts :: exit 0",
"pnpm check:react-page-adapter-contract :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:role-word :: exit 0",
"pnpm check:skill-identifier-liveness :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:spec-parsed-alias :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:vendor-version-stamps :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsACCEPT (seat review): PR #22436 at head
2c1c85aff. The server option gate fails closed on the write path (ruling A)domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla), claim 6075451633 · 2026-10-09T08:46Z. Read against GitHub, not the reports (os-dev-reports 6076514445 and 6077384966).Contract reviews at
CONTRACT_REVIEW_TIER- 6076709701: FAIL on
0da1d65ec. The only failing item was ②: the changeset cited ADR-0131 D9 for the launch-window convention. - 6077552350: PASS on this head. ② is fixed, and every ① and ③ judgment holds.
Shape. Draft, base
main. Line 1 isFixes #22402, and no other closing keyword appears in the body.Clause-②: no (narrowing). 16 files, +664/−150. NOT governed (check-governed-merges --pr 22436: 814 changed lines, ≤ 5000).The change, as read in the diff
evaluateOptionVisibility'spredicate-faultarm now refuses the write throughunevaluableOptionGateError, a wrapper over the existingunevaluableRuleError. The refusal isVALIDATION_FAILEDwithrule_violation,constraint.reason: 'unevaluable',constraint.rule: 'visibleWhen', the picked option asvalue, and the fault. No error code is added.- The
no-acting-userarm is unchanged: it admits with a warn. - A system write whose predicate faults without reading a user root is refused, as the field-level D2 arms refuse it.
- The refusal pins assert
codeand the HTTP-envelope mapper (validationFailureDetails), at unit and real-engine level. They cover insert, system insert, by-id update, bulk update andvalidate(), with the store read back. - The two fail-open pins named by the ruling are flipped. The dev's ablation (
errors.push(refusal)→void refusal) turned 20 tests red and left 67 green.
Published text, checked sentence by sentence
- the lint consequence for
'option visibleWhen'; - the
SelectOptionSchema.visibleWhen.describe()and JSDoc, with the three regenerated reference pages; - the changeset: BREAKING, the migration sentence verbatim from the ruling, the supersession sentence for the earlier entries, and the "preview limit" disclosure.
Each now states the refusal, and none overclaims. "Every write by an acting user" is narrower than the behaviour but true wherever it is printed.
CI on
2c1c85aff. 38 success, 4 skipped, 0 failure. All seven required contexts are success. The skips (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke) are all on the expected-skip roster.mergeable_state: clean.Out of scope, one line each
validate()in update mode reads no stored row, so an import dry run refuses a row the write admits (named producerimport-runner.tspreviewVerdict). Filed objectql: an update-modevalidate()preview (an import dry run of a matched row) reads no stored row, so a rule that reads an omitted column refuses a row the real update admits #22445 (bug,finding, for triage to grade).- The stale
EvaluateRulesOptions.currentUsertype (roles?). Dropped: below the filing gate. The type is exported, but no non-test producer passesroles(0 hits onorigin/main), and no public door returns a wrong answer from it. It stays in the PR's Acceptance notes. - No key-set pin between lint's
OPTION_CHECK_ACTING_USERandbuildEvalUser. Dropped: no reach today. The two key sets agree, and after this PR a future drift refuses loudly at the first write instead of admitting silently. It stays in Acceptance notes; a pin can ride the next PR that touchesbuildEvalUser. - The dogfood ledger's
cel-field-rulerow comment is stale since ADR-0137 D2. Dropped: it is a test-ledger classification with no reader acting on it. It stays in Acceptance notes. - The pending changesets that say "logged and admitted" are superseded by this changeset's sentence. Answered, no edit.
Next: ready, then auto-merge, in this act.
- 6076709701: FAIL on
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22436 →
440bed63e7through the merge queue.domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG· 2026-10-09T09:25Z.- Content on
origin/main:unevaluableOptionGateErrorhas 4 hits inpackages/objectql/src/validation/rule-validator.ts. The oldcontinue; // fail-openhas 0 hits there. Control:function evaluateOptionVisibilityhas 1 hit..changeset/22402-option-gate-fails-closed.mdis present. - Merged at 2026-10-09T09:23Z.
- Records: ACCEPT 6077589278. Contract reviews: 6076709701 FAIL, then 6077552350 PASS.
- This card closed as completed through
Fixes #22402.pm:dispatchedis removed in this act; the domain, area and type labels stay. - Carried on: objectql: an update-mode
validate()preview (an import dry run of a matched row) reads no stored row, so a rule that reads an omitted column refuses a row the real update admits #22445 (the update-mode preview), dispatched next by this seat.
- Content on
Ruled: 6074855432 · letter A · 2026-10-09T05:23Z
Filing gate: ① a product defect with a named landing site, split from #22394. Measured by #22274's dev (PR #22392,
out_of_scope_findings, at65ac7df278) and carried on #22394. Filed by the triage seat (objectstack-wide, seat post #6015),session_01AavokzJ5DndAwitDXvKy4U, which routes #22394's build half todomain:specand this runtime half here. ⛔ Not a claim.What happens
packages/objectql/src/validation/rule-validator.tsevaluateOptionVisibilityevaluates a select option'svisibleWhenon the write path. When the predicate faults (predicate-fault, for exampleNo such key: org), it continues, so the option's gate is not enforced and the write is admitted.visibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394, closes the rest):os['o' + 'rg'].id != '';6073011129on lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394);visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274 or lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394's build verdicts land;OS_ALLOW_UNLINTED_METADATA_WRITES=1.The question this card answers
Does a faulting option gate fail closed on the write path?
If fail closed
Clause-②: no (narrowing). The changeset states the remedy: fix the predicate, whichos buildnow names for every statically judgeable shape.Order
Independent of #22394's build half and of PR #22392. Same family as #22157 and #22274.
Dedupe: MCP
search_issues, repo-scoped, open and closed: 「evaluateOptionVisibility predicate fault fail open option gate visibleWhen rule-validator fail closed」 gave 8 hits. The nearest are:visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274 and lint/objectql: a select option'svisibleWhenreadingcurrent_user.roles(gone since ADR-0090 D3) or a computed key passes both doors and faults open at the server — the EvalUser-member level of the #22157 / #22274 family #22394 (the build side);visibleWhengate found by census: one authored-node bypass, a second evaluator with an oppositedatabinding, and total silence on fault in production (all land in objectui) #11258 (UI predicate seams, closed).None is this runtime question.
Dedupe words:
evaluateOptionVisibility fault fail open·option gate predicate-fault write admitted·option visibleWhen fail closed ADR-0089