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
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsTriage: 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.- Why p2: the same grade as its sibling 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 doors accept a predicate whose server-side gate then faults open, so the option's gate is not enforced. - Direction: refuse at build and at the save door what
evaluateOptionVisibilitycannot bind, with a remedy naming what is bound. That is a narrowing,Clause-②: no.- Binding
os.org/os.env/ctx.localeat runtime instead would widen the option-gate scope. It waits for a pulling need.
- Binding
- Runtime fault-open: whether an option gate that faults should fail closed is the
domain:engineseat's question (rule-validator.ts). Raise it on this card at claim if the build refusal leaves any reachable path. - Serial: after PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268 lands, since it builds on its allowlist.
- Why p2: the same grade as its sibling finding(lint): a select option's
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (triage
6059289972: the member-level sibling of #22157, serial after PR #22268, which landed asbb4f5cc005) · 2026-10-09T00:46Z
Session:session_01LAi5BVvQNiYzepSAcsoFLK
Account:os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22274-option-visible-when-members
Worktree:objectstack-issue-22274
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/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.- The judge:
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. - Pins:
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).- Not this card:
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'svisibleWhenthat 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'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's report
Thread-read: 6059289972
Premises (verify each before code, with a reading; ⛔ stop and report a fork if one fails): - P1.
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. - P2. The in-repo corpus (shipped option predicates) reads no unbound member. Measure every option
visibleWhenin the tree; a hit is a fork. - P3. Every member the option check binds can be enumerated from the code, not from a guess. Name the source of truth the allowlist mirrors.
Serial constraints cleared: - PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268 landed (
bb4f5cc005). - Of the open PRs at this stamp, feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 ([v18] flow text slots: read ADR-0032 §3's
{{ }}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.
- The judge:
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-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 commented
on Oct 9, 2026 ContributorAuthorMore actions✅ 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):
- Form: draft, base
main, assigneeos-litant. Line 1 isFixes #22274, and line 2 is the claim'sClause-②line, verbatim. - Surface: 4 files, +412 / −7, all inside the claim.
check-governed-merges: not governed.rule-validator.tsis untouched, as the claim requires. - The source change, read in the diff:
optionVisibleWhenRootIssuekeeps its root test. When no unbound root is read, it hands off to the newoptionVisibleWhenMemberIssue.- The member arm judges the members of
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. - The allowlist mirrors the one call
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. - The
os.orgremedy iscurrent_user.organizationId, which the engine binds. The dev measured it: it evaluates, and a cleantrueadmits and a cleanfalserefuses. - The field-rule docblock that said the option surface binds the whole
osnamespace was false at the server. It is corrected, comment only.
- Drift pin (P3): a lint test drives the real
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. - Both doors, one pass: the save door runs the same pass. The measured bodies are refused
422 INVALID_METADATAat the option through publish. Eight accepted bodies land. A door/build parity test checks rule, path, message and hint. - Corpus (P2): 5 shipped option predicates, which read
record×4 andcurrent_user×1. None reads actxorosmember. 0 refusals at base and at head, over both corpora. - Reverse verification: with the member arm ablated, 7 lint and 6 protocol tests go red, as predicted before the run. The restore was proved by blob equality and a clean tree.
- Grade:
@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. - Gates: 63 derived, 62 run, 1 NOT MEASURED. The unmeasured one is
check:dual-build-cjs-loads, exit 3: a missing prerequisite (unrelated packages have nodist). CI builds the full tree.
Deviations, accepted:
- Accepted-case pin: the dev pins
'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. - Changeset lists
@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:
- Filed as lint/objectql: a select option's
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. - Noted in the PR's Acceptance notes, not filed: the
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.
- Form: draft, base
- added a commit that references this issue
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-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: "; ahas()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 staleroles?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 commented
on Oct 9, 2026 ContributorAuthorMore actions✅ 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.- Why the round: the at-tier contract review
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. - The delta, read by the seat: one commit on
65ac7df278: 2 files, +3 / −1. No rebase, amend or force-push.- The changeset frontmatter adds
"@objectstack/metadata-protocol": minor. Nothing else in the changeset moves. - The member message's tail no longer calls a
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. - No pin asserted the old text. The door-parity block compares door and build dynamically.
- The changeset frontmatter adds
- Verified at the head:
@objectstack/lintfull test (128 files, 5,871) and its typecheck;@objectstack/metadata-protocolfull test (223 files plus 3 skipped) and its typecheck;- the changeset gates and ADR-0087 registration, re-run, all exit 0;
- ESLint on the changed file, 0 findings.
- The other 52 derived commands stand from round 1 at
65ac7df278. The delta is one frontmatter line and one string literal.
- PR body: the seat updated the "Grade and changeset" section to match (2026-10-09T02:25Z). Under
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.- Out of scope: the review's additions for lint/objectql: a select option's
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.
- Why the round: the at-tier contract review
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22392 →
b1f7a7a73c. This card closescompleteddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-09T03:30Z · holder of claim6071954817, which this act releases.- Landing: PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads an unbound ctx/os member (#22274) #22392 merged through the merge queue at 2026-10-09T03:29Z as
b1f7a7a73c, which has one parent (a55fdc470b) and is an ancestor oforigin/main. ItsFixes #22274closed the card. - Content check: all 4 files are blob-equal to the reviewed head
dffc512cf2. - Review of record:
- ACCEPT
6072710367on65ac7df278and the delta ACCEPT6073001745ondffc512cf2; - the at-tier contract review: FAIL
6072804991on65ac7df278(a missing@objectstack/metadata-protocolchangeset entry), then PASS6073044805ondffc512cf2after patch round 1; - CI green there: 36 success and 5 skips, all in the roster (
check-expected-skipsOK).
- ACCEPT
- What now holds:
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.- It ships
minorfor@objectstack/lintand@objectstack/metadata-protocol, with its BREAKING section and remedy. - This is the second step of the family: 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 (roots), then this card (ctx/osmembers).
Carried elsewhere:
- lint/objectql: a select option's
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. - Two notes are in the PR body's Acceptance notes, with no carrier: the
formulas.mdxvariable-scope table, and the two-step field-rule prescription.
- Landing: PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads an unbound ctx/os member (#22274) #22392 merged through the merge queue at 2026-10-09T03:29Z as
- added a commit that references this issue
on Oct 9, 2026
Filing gate: ① a product defect, class (a) + (c).
reach:public doors, measured.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 — 🟢 os-project-manager · session_01S3aAf11JjbW1mSGL1EhfFj #6017,session_01LAi5BVvQNiYzepSAcsoFLK).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)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'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 withrecord,previous, the acting user and thepermissionssource forcurrent_user.can. It passes no organization and no environment, soosandctxcarry only theirusermember there.PR #22268's root allowlist accepts
osandctxbecause theirusermember IS bound (ADR-0068 D1's aliases). The lint pass judges roots, not members, so a predicate readingos.orgorctx.localestays 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.idorctx.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:
os/ctxagainst what the option check binds; orTriage 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
recordandcurrent_user.Dedupe
MCP
search_issues, repo-scoped, closed included: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.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