Repository navigation
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
Description
Filing gate: ① a product defect, class (a) + (c). reach: public doors, measured.
- Where it came from: reported by finding(lint): a select option's
visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157's dev on PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268 (report6058210167,out_of_scope_findings[0]), measured at head012e8d7dbe. Read in source and filed by thedomain:specseat 1 (seat post [PM seat] domain:spec — 🟢 huangyiirene · session_01YRp5z4kK6vgYkrdH97ztsy #6017,session_01LAi5BVvQNiYzepSAcsoFLK). - Status of this card: ⛔ not graded or routed here; ⛔ not a claim.
- How it relates to finding(lint): a select option's
visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157: a sibling of finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157, same seam. finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157 refuses the unbound ROOTS (parentand the rest). This card is the unbound MEMBERS of roots that ARE bound.
What was measured (at 012e8d7dbe, PR #22268's head)
- The doors accept it.
os build/os validateand the object save door (saveMetaItempublish,PUT /api/v1/meta/object/:name) accept these select optionvisibleWhenpredicates with 0 findings:os.org.id != ''os.env == 'prod'ctx.locale == 'en'
- The runtime cannot enforce it. The built
evaluateValidationRules, with an authenticated caller, admitsos.org.id != ''andctx.locale == 'en'with reasonpredicate-fault(No such key: org/No such key: locale), so the option's gate is not enforced.os.envwas not run.
Where it is (read in source)
evaluateOptionVisibility (packages/objectql/src/validation/rule-validator.ts) evaluates with record, previous, the acting user and the permissions source for current_user.can. It passes no organization and no environment, so os and ctx carry only their user member there.
PR #22268's root allowlist accepts os and ctx because their user member IS bound (ADR-0068 D1's aliases). The lint pass judges roots, not members, so a predicate reading os.org or ctx.locale stays green at both doors.
Seam: spec:SelectOptionSchema.visibleWhen → runtime:evaluateOptionVisibility.
Why it matters
The failure is the same one #22157 closes for roots. A server-enforced option gate is declared, passes every door, and is silently not kept at write time. An AI that writes os.org.id or ctx.locale (both valid at other evaluation sites) gets a green build and a gate that never fires.
The question this card carries
The fix needs a member-level shape. Either:
- the option pass judges the members of
os/ctxagainst what the option check binds; or - the option check binds the organization and environment members.
Triage chooses. The build-side direction is the narrowing one. The corpus is measured first, and on PR #22268's measurement the 5 in-tree option predicates read only record and current_user.
Dedupe
MCP search_issues, repo-scoped, closed included:
- 「select option visibleWhen os.org ctx.locale os.env member unbound evaluateOptionVisibility fail open predicate fault」: 5 hits. The nearest is finding(lint): a select option's
visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157, the root sibling. A per-optionvisibleWhenwritten in a*.form.tsis silently inert — the metadata-admin renderer never reads it #11793 (closed) is the inert*.form.tsoptionvisibleWhen. Neither is this card. - 「option predicate member-level root os.org bound by option check build accepts runtime No such key org」: 7 hits. finding(lint): a select option's
visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157 is the nearest, and the rest are other slots. None is this card.
Dedupe words: option visibleWhen os.org ctx member unbound fail-open · option predicate member-level root os.env · evaluateOptionVisibility no org env bound
Activity
objectstack-fleet commentedon Oct 8, 2026
Triage: first grade,
bug·priority:p2·domain:spec·area:records·pm:queue. Direction: the build door refuses an optionvisibleWhenmember the option check never binds, as #22157 does for rootsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T11:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/lint(the option-predicate allowlist #22157 / PR #22268 adds, extended from roots to members) ⇒domain:spec; rationale:packages/lintis that lane's.visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157. The doors accept a predicate whose server-side gate then faults open, so the option's gate is not enforced.evaluateOptionVisibilitycannot bind, with a remedy naming what is bound. That is a narrowing,Clause-②: no.os.org/os.env/ctx.localeat runtime instead would widen the option-gate scope. It waits for a pulling need.domain:engineseat's question (rule-validator.ts). Raise it on this card at claim if the build refusal leaves any reachable path.objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 1 (triage
6059289972: the member-level sibling of #22157, serial after PR #22268, which landed asbb4f5cc005) · 2026-10-09T00:46ZSession:
session_01LAi5BVvQNiYzepSAcsoFLKAccount:
os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22274-option-visible-when-membersWorktree:
objectstack-issue-22274Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/mainbb4f5cc005; stop on breach and explain in the report). Triage's direction: the build door and the object save door refuse, with a remedy naming what IS bound, an optionvisibleWhenmember thatevaluateOptionVisibilitynever binds (os.org.*,os.env,ctx.localeand the like), as #22157 does for roots. ⛔ Not binding those members at runtime: that would widen the option-gate scope and waits for a pulling need.packages/lint/src/validate-expressions.ts. It extends PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268'sOPTION_VISIBLE_WHEN_BOUND_ROOTS/optionVisibleWhenRootIssuefrom roots to the members of the bound alias roots (os,ctx,user,current_user), measured against whatevaluateOptionVisibilityactually passes. It stays in the same pass, soos buildand the save door agree.validate-expressions.test.ts, and the save-door twinpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts..changeset/22274-*.md, graded as AGENTS.md says for a narrowing (BREAKING section with the remedy).rule-validator.ts's fault-open behaviour (domain:engine's question per triage). It is raised on this card if the build refusal leaves a reachable path.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate"). A narrowing of a published accept set owes a contract-review-tier review before enqueue. It comes from an isolated at-tier subagent.Clause-②: no (narrowing: a select option's
visibleWhenthat reads an unbound member of a bound root is refused at build and at the object save door)Responsibility: n/a, a defect card filed by this seat from finding(lint): a select option's
visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157's reportThread-read: 6059289972
Premises (verify each before code, with a reading; ⛔ stop and report a fork if one fails):
evaluateOptionVisibilitybindsrecord,previous, the acting user (ascurrent_userand its aliases'usermember), and thepermissionssource forcurrent_user.can, and no organization or environment. Soos.org.*,os.envandctx.localefault at runtime, and that fault admits the write.visibleWhenin the tree; a hit is a fork.Serial constraints cleared:
bb4f5cc005).{{ }}delimiter instead of single-brace{token}(notify title/message and the other flow string slots), converting only what renders the same #22110, the flow text-slot pass) touchesvalidate-expressions.tsin another region. Ordinary concurrency: whichever lands later mergesmain.fix(lint)!: refuse an option visibleWhen member of ctx/os the option …
test(lint): pin the option visibleWhen member verdict at build and at…
docs(changeset): state the corpus reading without tree-dependent coun…
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22274,
"status": "done",
"branch": "claude/issue-22274-option-visible-when-members",
"pr": "#22392",
"head": "65ac7df278",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (the dispatching seat's session; this run is its subagent)",
"premise_still_valid": true,
"summary": "The option-predicate verdict in packages/lint/src/validate-expressions.ts now judges members as well as roots. optionVisibleWhenRootIssue keeps its root test; when no unbound root is read it hands off to the new optionVisibleWhenMemberIssue, which refuses at error, located at the option, any member of ctx or os other than user (OPTION_VISIBLE_WHEN_BOUND_MEMBERS = { ctx: [user], os: [user] }), read through formula's analyzeRelationshipTraversals so os.org, os.?org, os['org'] and has(os.org) are one read. The message names the member, says the option check binds only the user member under that root, and gives a per-member remedy: for os.org it is current_user.organizationId, which the engine binds (measured: evaluates, clean true admits, clean false refuses). The object save door runs the same pass, so both doors agree (pinned by a parity test); the runtime is untouched per the ruling. A field-rule docblock that claimed the option surface binds the whole os namespace (false at the server) was corrected, comment only. Changeset: @objectstack/lint minor, BREAKING section with remedy, ADR-0087 not-required (no-migration-prescription).",
"premises": {
"P1": "HELD. Through the built @objectstack/objectql evaluateValidationRules (insert, authenticated caller with organizationId, permissions passed) at base bb4f5cc: os.org.id != '' -> admitted, predicate-fault No such key: org; os.env == 'prod' -> admitted, predicate-fault No such key: env; ctx.locale == 'en' -> admitted, predicate-fault No such key: locale. Controls os.user.id, ctx.user.id, current_user.id, user.id, record.x, current_user.can evaluated cleanly; os.user.id == 'nobody' refused VALIDATION_FAILED (gate runs).",
"P2": "HELD, no fork. Corpus A (every tracked .object.ts under packages/* and examples/** plus the two app-multi-package sub-stacks): 112 files / 119 objects at base bb4f5cc, 111 / 118 at merged head 4e0f473 (origin/main 117d34d retired one), 0 import or parse failures. Corpus B (example stacks as defineStack composes them): 33 objects. Both: 5 option predicates, all on showcase_cascade, roots record x4 and current_user x1, members read under os and ctx: none; option findings 0 at build and door at both trees. Tree-wide text grep for visibleWhen with an os./ctx. member read: 5 lines, none an option predicate (a lint fixture, a page visibleWhen, three spec describe strings).",
"P3": "HELD. The allowlist mirrors (1) the one call evaluateOptionVisibility makes, ExpressionEngine.evaluate(expr, { record: merged, previous, user, permissions }); (2) formula buildScope, which from that context mounts ctx = { user } and os = { user }, and os.org / os.env only from an org / env in the context; (3) ADR-0068 D1 aliases. current_user and user ARE the EvalUser, the same at every site, so they carry no member map. Drift surfacing: a new lint test drives the real buildScope and ExpressionEngine.evaluate with the option check context and pins accepted = [ctx.user, os.user] (accepted and evaluate) and refused = [os.org, os.env] (refused and fault), so buildScope mounting a new member there or dropping user turns it red. Not caught mechanically: an ObjectQL call-shape change (the option check starting to pass org), because lint cannot depend on ObjectQL; that direction rests on the constant docblock rule and ObjectQL USER_SCOPE_ROOTS docblock."
},
"tests": "All at 65ac7df (merge of origin/main 117d34d; this PR's files untouched by the merge; pnpm install --frozen-lockfile + rebuild of the @objectstack/metadata-protocol... closure first). pnpm --filter @objectstack/lint test: 128 files, 5871 passed. pnpm --filter @objectstack/metadata-protocol test: 223 files passed + 3 skipped, 28325 passed + 19 skipped (pre-existing skips). pnpm --filter @objectstack/lint typecheck: tsc --noEmit OK, check:test-typecheck OK (validate-expressions.test.ts carries no debt). pnpm --filter @objectstack/metadata-protocol typecheck: OK (tsc --listFiles includes the protocol test). New pins: lint #22274 describe, 9 tests (3 refusals, the os.org replacement, CONTROL, POSITIVE CONTROL os.org.id on a formula field not refused, member spellings, ordering, buildScope parity); protocol #22274 block, 7 tests ((a) x3 publish save 422 INVALID_METADATA at the option, nothing lands; (b) 8 accepted bodies land active; (c) x3 door/build parity on rule, where, path, message, hint). Ablation from committed f270d45 via scripts/ablation-replace.mjs WRAP mode plus outer trap restore on EXIT INT TERM by absolute path: member-arm call gated on Reflect.has(Object, "ablation22274"); anchor x1 -> x0, blob 256380b4d7c8 -> 057b663b2c3a, on-disk anchor 0 / marker 1. Prediction recorded before the run: lint 7 red, protocol 6 red. Observed: lint 7 failed / 360 passed (the predicted seven); lint rebuilt, ablation-dist-preflight marker present in 4 built files; protocol 6 failed / 120 passed (the predicted six). Restore: blob 256380b4d7c8 == HEAD, git diff HEAD empty, rebuild, preflight --absent marker gone from all 20 built files and tree clean, lint 367/367 and protocol 126/126.",
"gates": "Derived at 65ac7df with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 63 commands, identical to the claim-time list. 62 exit 0. pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (unrelated packages have no dist/ locally) -> NOT MEASURED: dual-build-cjs-loads, reason: prerequisite not met locally; CI builds the full tree. --ran with exit codes recorded: 63 derived, 62 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. ESLint narrowed to the 3 changed .ts files (--no-inline-config --format json): 3 files, 0 errors, 0 warnings; population from eslint.config.mjs (/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED and packages/spec/), no parserOptions.project so no type-aware linting, so untouched files cannot move; repo-wide pnpm lint is CI's. CI not awaited (in_progress at report time).",
"line_budget": "419 changed lines (+412 / -7) over 4 files vs merge base 117d34d, under the 5000 human-merge threshold",
"files_changed": [
"packages/lint/src/validate-expressions.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts",
".changeset/22274-option-visible-when-members.md"
],
"deviations": [
"Zone 3 pin list: ctx.user.roles is accepted by this verdict (it stops at the first member), but measured through the built engine it faults (No such key: roles; ADR-0090 D3 renamed roles to positions), so pinning it as an accepted case would endorse a never-enforced gate. The accepted-case pin uses 'org_admin' in ctx.user.positions instead; the EvalUser member level is filed below as a same-family finding.",
"Changeset lists @objectstack/lint only, as the dispatch named it; no published file of @objectstack/metadata-protocol moves. The sibling #22157 changeset also listed metadata-protocol (minor) for its door; the PM may align.",
"Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body ends with the session-URL footer, per AGENTS.md and os-dev; the harness attribution reminder asked for a model-named trailer and a different PR footer, which were not used.",
"Nothing else: no file outside the claim surface, rule-validator.ts read only, no skip/loosened timeout, no MCP write."
],
"mcp_calls": "0 - no MCP tool called",
"api_writes": "3 REST writes, each a POST /repos/objectstack-ai/objectstack/dispatches through the fleet-write relay executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft, #22392, body read back identical, 13758 bytes); (2) label-write --assign os-litant = POST /repos//issues/22392/assignees (read back: assignee os-litant; labeler labels documentation, size/m, tests, tooling untouched); (3) post-stamped os-dev-report = POST /repos//issues/22274/comments. git push is not a REST write. Reads used gh api (issue, comments, PR read-back).",
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: build door validateStackExpressions (the os build rule, also run by the object save door) gives 0 findings for an option visibleWhen of 'admin' in current_user.roles and of ctx.user.roles == ['a'] (measured at 65ac7df), and the built evaluateValidationRules admits both with predicate-fault No such key: roles · evidence: EvalUser has no roles member since ADR-0090 D3 (positions); the option gate is never enforced; this is the EvalUser member level of the same family as #22157 (roots) and #22274 (ctx/os members), so fold it into the family closing card rather than a single-point card · dedupe words: option visibleWhen current_user.roles EvalUser member fault-open · ctx.user.roles positions ADR-0090 predicate-fault · EvalUser member allowlist option predicate",
"class: a · reach: build door validateStackExpressions gives 0 findings for the option visibleWhen os['o' + 'rg'].id != '' (a computed key names no member, so no static verdict can judge it), and the built evaluateValidationRules admits it with predicate-fault No such key: org (measured at 65ac7df) · evidence: the reachable path Zone 1 asked to be raised on this card for domain:engine's question (rule-validator.ts option fault-open); not measured: rows stored before this change, OS_ALLOW_UNLINTED_METADATA_WRITES=1 writes, and the EvalUser level above · dedupe words: option visibleWhen computed key fault-open · evaluateOptionVisibility predicate-fault fail closed",
"carrier: none · noted, not filed: content/docs/data-modeling/formulas.mdx variable-scope table lists os.org / os.env as available in predicates, broader than the option check binds (in PR Acceptance notes).",
"carrier: none · noted, not filed: the field-rule user-tier prescription sends a field-level os.org.id predicate to an option visibleWhen, where it now meets this refusal naming current_user.organizationId; two steps, no false message (in PR Acceptance notes)."
],
"cleanup": "The worktree ../objectstack-issue-22274 is removed right after this comment is posted (node_modules first, then git worktree remove without --force). No dev server or background monitor was started; the one background gate runner (nohup) exited before this report."
}
objectstack-fleet commentedon Oct 9, 2026
✅ ACCEPT on the diff: PR #22392 at
65ac7df278. Landing held for the at-tier contract review and CIdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-09T01:59Z · holder of claim6071954817; the review of record for the dev's report on this card.Checklist (read on GitHub and in the PR's own diff):
main, assigneeos-litant. Line 1 isFixes #22274, and line 2 is the claim'sClause-②line, verbatim.check-governed-merges: not governed.rule-validator.tsis untouched, as the claim requires.optionVisibleWhenRootIssuekeeps its root test. When no unbound root is read, it hands off to the newoptionVisibleWhenMemberIssue.ctxandosagainstOPTION_VISIBLE_WHEN_BOUND_MEMBERS = { ctx: ['user'], os: ['user'] }. The members come from@objectstack/formula'sanalyzeRelationshipTraversals, soos.org,os.?org,os['org']andhas(os.org)count as one read.evaluateOptionVisibilitymakes ({ record, previous, user, permissions }) and whatbuildScopemounts from it. The dev measured P1 through the builtevaluateValidationRules:os.org.id,os.envandctx.localeeach fault, and the write is admitted.os.orgremedy iscurrent_user.organizationId, which the engine binds. The dev measured it: it evaluates, and a cleantrueadmits and a cleanfalserefuses.osnamespace was false at the server. It is corrected, comment only.buildScopeand evaluator with the option check's context. It pinsctx.userandos.useras accepted and evaluating, andos.organdos.envas refused and faulting. IfbuildScopestarts or stops mounting a member there, the test turns red. One direction is not caught mechanically: the option check starting to passorg, because lint cannot depend on ObjectQL. That direction rests on the docblock's ⛔ rule.422 INVALID_METADATAat the option through publish. Eight accepted bodies land. A door/build parity test checks rule, path, message and hint.record×4 andcurrent_user×1. None reads actxorosmember. 0 refusals at base and at head, over both corpora.@objectstack/lintminorwith a BREAKING section, the remedy and the ADR-0087not-requireddisposition. That is the narrowing grade, as for finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157.check:dual-build-cjs-loads, exit 3: a missing prerequisite (unrelated packages have nodist). CI builds the full tree.Deviations, accepted:
'org_admin' in ctx.user.positions, notctx.user.roles.rolesfaults at runtime (ADR-0090 D3 renamed itpositions), so pinning it as accepted would endorse a gate that is never enforced. The verdict stops at the first member by design, so theEvalUsermember level is a separate question.@objectstack/lintonly. No published file of@objectstack/metadata-protocolmoves; the door's new refusal reaches it through its@objectstack/lintdependency. The finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157 changeset also listedmetadata-protocol(minor). The contract review is asked whether the door's consumers need their own entry.Out of scope:
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: an optionvisibleWhenthat reads a missingEvalUsermember (current_user.roles,ctx.user.roles), or readsosthrough a computed key, passes both doors and faults open. The build half is spec; the runtime fail-closed half is engine.formulas.mdxvariable-scope table listsos.org/os.envmore broadly than the option check binds them. The field-rule user-tier prescription takes two steps to reach this refusal.fix(lint): list metadata-protocol in the changeset; state both direct…
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22274,
"round": "patch round 1: contract review 6072804991 on 65ac7df (finding 1 blocking, nit from judgment 6)",
"status": "done",
"branch": "claude/issue-22274-option-visible-when-members",
"pr": "#22392",
"head": "dffc512cf2 (one commit on 65ac7df; no rebase, no amend, no force-push)",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (the dispatching seat's session; this run is its subagent)",
"premise_still_valid": true,
"summary": "Finding 1 (blocking): .changeset/22274-option-visible-when-members.md now lists "@objectstack/metadata-protocol": minor beside "@objectstack/lint": minor in its frontmatter; nothing else in the changeset moved. Nit: the tail of optionVisibleWhenMemberIssue's message no longer says the has()/optional spelling is admitted. It now reads: "; a
has()test or an optional read of it never finds it set, so the option is refused on every write instead, or admitted on every write when the test is negated or the read's default passes." That keeps the PM's requested words verbatim and adds one measured qualifier (see deviations). No pin asserted the old text (grep over both test files: 0 hits); the protocol parity block compares door and build messages dynamically and stays green. Delta vs 65ac7df: 2 files, +3 / -1.","tests": "All at dffc512, after a rebuild of the @objectstack/metadata-protocol... closure in a fresh worktree (pnpm install first), then a lint rebuild (new tail present in dist/index.js and dist/runtime.js). pnpm --filter @objectstack/lint test (full): 128 files, 5871 passed. pnpm --filter @objectstack/metadata-protocol test (full, includes protocol.runtime-authoring-gate.test.ts and its #22274 block): 223 files passed + 3 skipped, 28325 passed + 19 skipped (pre-existing skips). pnpm --filter @objectstack/lint typecheck: tsc --noEmit OK, check:test-typecheck OK. pnpm --filter @objectstack/metadata-protocol typecheck: OK. Measured for the wording, through @objectstack/formula ExpressionEngine.evaluate with the option check context { record, previous, user (with organizationId), permissions }: has(os.org) -> false (option refused every write); os.?org.hasValue() -> false (refused); has(ctx.locale) -> false (refused); !has(os.org) -> true (admitted every write); os.?org.orValue({}) == {} -> true (admitted); ctx.?locale.orValue("en") == "en" -> true (admitted).",
"gates": "Re-derived at dffc512 with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: the same 63 commands as round 1. check:future-spec-major is not derived. Run this round, exit codes captured before any pipe: check-adr-0087-registration --base origin/main (reads [BREAKING+bang+clause-②-narrowing], not-required no-migration-prescription) :: exit 0; check-adr-0087-registration --self-test :: exit 0; check-changeset-no-major --base origin/main :: exit 0; check-changeset-no-major --self-test :: exit 0; check-empty-changeset --base origin/main :: exit 0; check-empty-changeset --self-test :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0; pnpm check:doc-authoring :: exit 0; pnpm check:nul-bytes :: exit 0; pnpm check:objectui-changeset :: exit 0; pnpm check:pm-changeset-deadline-census :: exit 0. The other 52 derived commands were not re-run this round: the delta is one frontmatter line and one string literal, and round 1 ran all 63 at 65ac7df (62 exit 0, check:dual-build-cjs-loads NOT MEASURED, exit 3 prerequisite). ESLint narrowed to packages/lint/src/validate-expressions.ts (--no-inline-config --format json): 1 file, 0 errors, 0 warnings; same population and no-type-aware-linting reading as round 1. CI not awaited.",
"line_budget": "this round +3 / -1 over 2 files vs 65ac7df; whole PR 421 changed lines (+414 / -7) over 4 files vs merge base 117d34d, under the 5000 threshold",
"files_changed": [
".changeset/22274-option-visible-when-members.md",
"packages/lint/src/validate-expressions.ts"
],
"deviations": [
"Message tail wording: the requested sentence ("... never finds it set, so the option is refused on every write instead.") is kept verbatim as the leading clause, and a qualifier is added, ", or admitted on every write when the test is negated or the read's default passes". Reason, measured (tests field): a negated has() test (!has(os.org)) and a defaulted optional read (os.?org.orValue({}) == {}) evaluate cleanly to true at the option check, so the option is admitted on every write; the requested sentence alone would state the inverse of the reviewer's nit for those spellings. If the seat wants the exact requested text, it is a one-string change.",
"PR body not edited (conflict, named here, not chosen silently). The coordinator said to edit the body if it states the changeset's package list, and it does (section "Grade and changeset"). os-dev says the dev writes the PR body once, at pr_create, and never PATCHes it; corrections are named in the report for the seat to write, and os-dev outranks dispatch text on conflict. Exact edit for the seat: in "Grade and changeset", replace the first bullet's opening "lists
@objectstack/lintasminor, as the dispatch named it." with "lists@objectstack/lintand@objectstack/metadata-protocolasminor(metadata-protocol added in patch round 1 atdffc512cf2, per contract review6072804991: the BREAKING section names that package's doors).", and delete the second bullet, which starts "@objectstack/metadata-protocolis not listed." The body does not quote the old message tail, so nothing else in it needs to change. Its "Local verification" section names 65ac7df; this report carries the dffc512 runs.","origin/main moved 8 commits past 117d34d (to 11d119a). Two of them touch packages/lint (rule-explanations.ts, validate-functional-completeness.test.ts), and none touch this PR's files. git merge-tree of HEAD and origin/main is a clean textual merge (exit 0). Not merged this round, because the order asked for one commit on 65ac7df with no rebase; the queue rebuilds on main."
],
"mcp_calls": "0 - no MCP tool called",
"api_writes": "1 REST write this round: post-stamped os-dev-report = POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay, as objectstack-fleet[bot]) creating a comment on issue 22274. git push is not a REST write. No PR PATCH, no label or assignee write. Reads used gh api (ACCEPT 6072710367, contract review 6072804991, card comments).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22394 (already filed by the seat) · noted, not filed: contract review 6072804991 judgment 3 adds computed receivers, such as a comprehension variable ([os].all(o, o.org.id != '')) or a ternary, to the class of reads no static verdict can judge, beside the computed key. Judgment 4 adds the stale
roles?type on EvaluateRulesOptions.currentUser in rule-validator.ts. Both are for #22394's thread, not this PR."],
"cleanup": "The worktree ../objectstack-issue-22274 is removed right after this comment is posted (node_modules first, then git worktree remove without --force). No dev server, background job or monitor was started this round."
}
objectstack-fleet commentedon Oct 9, 2026
✅ ACCEPT on the delta: PR #22392 at
dffc512cf2(patch round 1). Landing held for the at-tier delta review and CIdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-09T02:25Z · holder of claim6071954817; the review of record for the patch-round report6072975174. The ACCEPT6072710367on65ac7df278stands for the rest of the change.6072804991on65ac7df278FAILed on one finding. The changeset's BREAKING section names@objectstack/metadata-protocol's doors answering 422, but the frontmatter did not list that package. Every door-crossing changeset in this seam lists it (formula: the metadata save door stores a formula that calls an unregistered function (sqrt), and every read answers null with nothing logged — the docs say the shared validator backs metadata registration #22019, finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032, finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157). This settles the seat's deviation 2 against the PR.65ac7df278: 2 files, +3 / −1. No rebase, amend or force-push."@objectstack/metadata-protocol": minor. Nothing else in the changeset moves.has()or optional read "unset". It now says the option is refused on every write, or admitted when the test is negated or the read's default passes. The dev's qualifier beyond the seat's wording is accepted: it measured both directions throughExpressionEngine.evaluatewith the option check's context (has(os.org)→false;!has(os.org)andos.?org.orValue({}) == {}→true), and the seat's sentence alone would have been false for the negated spellings.@objectstack/lintfull test (128 files, 5,871) and its typecheck;@objectstack/metadata-protocolfull test (223 files plus 3 skipped) and its typecheck;65ac7df278. The delta is one frontmatter line and one string literal.os-dev, the dev does not patch the body afterpr_create.main: it moved 8 commits past117d34de3f. None touches this PR's files, andgit merge-treeis clean. The queue builds onmain.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 are the computed-receiver shape and the staleroles?type onEvaluateRulesOptions.currentUser. They belong on that card's thread and are left to its holder.objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22392 →
b1f7a7a73c. This card closescompleteddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-09T03:30Z · holder of claim6071954817, which this act releases.b1f7a7a73c, which has one parent (a55fdc470b) and is an ancestor oforigin/main. ItsFixes #22274closed the card.dffc512cf2.6072710367on65ac7df278and the delta ACCEPT6073001745ondffc512cf2;6072804991on65ac7df278(a missing@objectstack/metadata-protocolchangeset entry), then PASS6073044805ondffc512cf2after patch round 1;check-expected-skipsOK).os buildand the object save door refuse a select option'svisibleWhenthat reads a member ofctxorosother thanuser(os.org.id,os.env,ctx.locale). The refusal names the member and what the option check binds there, and gives the remedy. Foros.org, the remedy iscurrent_user.organizationId. The runtime is unchanged.minorfor@objectstack/lintand@objectstack/metadata-protocol, with its BREAKING section and remedy.visibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157 (roots), then this card (ctx/osmembers).Carried elsewhere:
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 (awaiting triage) is theEvalUsermember level (current_user.roles,ctx.user.roles), plus the computed key or receiver that faults open. The contract review's two additions to it are pointed to in6073011129.formulas.mdxvariable-scope table, and the two-step field-rule prescription.fix(lint)!: os build and the object save door refuse a select option'…