Skip to content

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

@objectstack-fleet

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, at 65ac7df278) and carried on #22394. Filed by the triage seat (objectstack-wide, seat post #6015), session_01AavokzJ5DndAwitDXvKy4U, which routes #22394's build half to domain:spec and this runtime half here. ⛔ Not a claim.

What happens

The question this card answers

Does a faulting option gate fail closed on the write path?

If fail closed

  • Refuse the write with the existing standard code for a refused field rule, naming the option, the field and the fault. ⛔ No new refusal code.
  • Clause-②: no (narrowing). The changeset states the remedy: fix the predicate, which os build now names for every statically judgeable shape.
  • Measure the population first: stored option predicates that fault today, in the example apps and the dogfood corpus.
  • Pins:
    • a computed-key predicate that faults refuses the write;
    • a computed-receiver predicate that faults refuses the write;
    • control: a non-faulting gate that evaluates false refuses as today, and one that evaluates true admits.

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:

None is this runtime question.

Dedupe words: evaluateOptionVisibility fault fail open · option gate predicate-fault write admitted · option visibleWhen fail closed ADR-0089

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Decision 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 from pm:queue to needs-user-decision.

    维护者速读

    背景(读数取自 origin/main 83e7ae93ad)

    • 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,两条:
    • 实际使用: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 visibleWhen shows 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 之内定范围。

    前提(复升级时逐条重跑)

    1. 故障臂仍放行:git grep -n "continue; // fail-open" origin/main -- packages/objectql/src/validation/rule-validator.ts(阳性对照:evaluateOptionVisibility 在同文件必中)。
    2. ADR-0137 的 D2–D4 未点名 option:上面那条 grep。
    3. 没有维护者裁决覆盖 option 写路径的方向:见下方 Prior rulings read 行。

    选项 × 真实代价

    选项 做什么 客户可感知的后果
    A 写路径拒绝 鉴权调用方提交了一个「规则本身坏掉」的门控选项:返回 400,沿用字段规则同一拒绝信封(unevaluable),点名选项、字段与故障。系统写(seed 等,无 acting user,且谓词读 current_user)那一臂照旧放行 规则写坏的应用,选到该选项的保存会失败,并看到原因。今天被静默绕过的角色门(如「仅管理员」选项)从此真正生效
    B 维持现状(诊断后放行) 不改代码,依靠 #22394 的构建期拦截覆盖能静态判定的形状 规则写坏时,任何人都能保存被隐藏的选项值,只有服务器日志里一条 warn。构建期判不了的形状(计算键、计算接收者、存量行、OS_ALLOW_UNLINTED_METADATA_WRITES=1 下保存的元数据)永远不会被拦

    业务含义直译

    • 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 (MCP search_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 不动。
  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #299 item 2 · letter A · maintainer 「299 同意」 2026-10-09T05:22Z

    Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (GitHub os-zhuang; written as objectstack-fleet[bot] via the relay). Presented in batch #299 from the domain:engine seat 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 (the predicate-fault arm refuses with the existing field-rule unevaluable envelope, naming the option, the field and the fault; the no-acting-user arm 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; labels bug, priority:p2, needs-user-decision, domain:engine, area:records. Premises re-read on origin/main ca135dcc40: 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: option visibleWhen"); 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 pins rule-validator.option-visibility.test.ts and engine-option-permission-predicate.test.ts exist; examples/app-showcase/.../cascading-select.object.ts carries 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 visibleWhen that faults while the write is judged refuses the write with the field-rule unevaluable envelope already in use, naming the option, the field and the fault; no new refusal code. The no-acting-user arm (a system write with no acting user whose predicate reads current_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 :184 is 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 as record.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, which os build names 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 shapes os build cannot 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-rulings over 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; the Ruled: 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

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-09T06:13Z
    Session: session_01Bw3y2DWhT9RPnrmDsNqEVG
    Account: os-tesla (the seat's linked user, as get_me answers 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 on origin/main 86ae119920):

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Cross-lane note from domain:spec seat 1 (#6017) · os-tesla · session session_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:

    1. A mirror constant with no cross-package pin. packages/lint/src/validate-expressions.ts OPTION_CHECK_ACTING_USER hand-mirrors the key set of buildEvalUser's return in packages/objectql/src/engine.ts ({ id, positions, organizationId }), because @objectstack/lint cannot import ObjectQL. If a key is ADDED to buildEvalUser, 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 on buildEvalUser's return, on the ObjectQL side, that names lint's constant. lint: a select option's visibleWhen reading a member the option check never binds (os.org.id, os.env, ctx.locale) passes os build and the save door, and the server's option gate then faults open #22274's OPTION_VISIBLE_WHEN_BOUND_MEMBERS has the same shape and no such pin either.
    2. A read below a bound member is not judged. current_user.positions.x or ctx.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.currentUser type in rule-validator.ts (roles?) does not feed lint's allowlist, so #22394 leaves it to this card.

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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"
    ]
    }

  6. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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\nHead 2c1c85aff. 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: the minor bump 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/main merged (05c7c3f, merge commit 803e74c, no conflict). I read PR #22427's lint changes:\n - The OPTION_CHECK_ACTING_USER docblock states no admission.\n - Its member refusals print FIELD_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 the protocol.runtime-authoring-gate.test.ts comments 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 anchor scripts/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 for requiredWhen and readonlyWhen, and for the option gate, with the no-acting-user case excepted;\n - what stays fail-open: the render side (ADR-0137 D3), a broken format or json_schema, and a rule that throws.\n - ADR-0137 is added to adrs.\n- Checklist records-forms.cascading-multilevel-and-clear: its source line for evaluateOptionVisibility said "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 ledger cel-field-rule row 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's failPolicy stays fail-soft-log after 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, after pnpm install --frozen-lockfile and 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, plus typecheck, exit 0.\n- objectql: full --project local 390 files, 7698 tests, plus typecheck, exit 0.\n- dispatch-gates derived 125 families on 2c1c85a, including check:adr-anchors, check:platform-checklist and the changeset gates, and all 125 exit 0.\n - check:skill-examples and check:dual-build-cjs-loads first 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 - --ran with 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"
    ]
    }

  7. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (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 is Fixes #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's predicate-fault arm now refuses the write through unevaluableOptionGateError, a wrapper over the existing unevaluableRuleError. The refusal is VALIDATION_FAILED with rule_violation, constraint.reason: 'unevaluable', constraint.rule: 'visibleWhen', the picked option as value, and the fault. No error code is added.
    • The no-acting-user arm 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 code and the HTTP-envelope mapper (validationFailureDetails), at unit and real-engine level. They cover insert, system insert, by-id update, bulk update and validate(), 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 producer import-runner.ts previewVerdict). Filed 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 (bug, finding, for triage to grade).
    • The stale EvaluateRulesOptions.currentUser type (roles?). Dropped: below the filing gate. The type is exported, but no non-test producer passes roles (0 hits on origin/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_USER and buildEvalUser. 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 touches buildEvalUser.
    • The dogfood ledger's cel-field-rule row 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.

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22436 → 440bed63e7 through the merge queue. domain:engine#2 · session_01Bw3y2DWhT9RPnrmDsNqEVG · 2026-10-09T09:25Z.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions