Repository navigation
finding(lint): a select option's visibleWhen reading parent passes os build and the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsPath: ③ 验证:响亮拒绝错的,放行对的 — a gate the runtime cannot hold is refused at build | 缺项 | P2
Triage: first grade,
bug·priority:p2·domain:spec·area:records·pm:queue(findingremoved). Direction: the build and the save door refuse an optionvisibleWhenthat readsparent, while the runtime cannot enforce itTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:02Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/lint(the rule setos buildand, since PR #22151, the save door run) ⇒domain:spec; rationale:packages/lintis the spec seat's by the anchoring exception (SKILL.md domain table). Read onmainec8f37c890.- Why p2: a declared option gate passes every door and then is not enforced at run time ("allowed through; the option's gate was NOT enforced", measured). ADR-0049: a declared property the runtime does not enforce is worse than absent.
- Direction:
- refuse
parentin a select option'svisibleWhenat the build and the save door, with a message naming the field and the root - if the runtime later supports
parentthere, the refusal is lifted in the same change
- refuse
- Pins: the measured body is refused at both doors; control: an option
visibleWhenreadingrecordpasses. Clause-②: no(a narrowing). Patch changeset for@objectstack/lint.
- 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 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (triage's direction
6052678360; released by #22032's closing pass,73a0a6bf1d) · 2026-10-08T09:18Z
Session:session_01LAi5BVvQNiYzepSAcsoFLK
Account:os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22157-option-visible-when-parent
Worktree:objectstack-issue-22157
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main73a0a6bf1d; stop on breach and explain in the report):packages/lint/src/validate-expressions.ts: the option loop inrunStackExpressionPassesgains a root verdict over the rootsevaluateOptionVisibilitybinds (record,previous, the acting user,permissions). It refusesparentwith a message naming the field, the option and the root, located as the option slot is today. Since pass 3 (aa9447c70b) the save door runs the same pass, so one change covers both doors.- Its tests in
packages/lint/src/(the option-visibility door suite and a build-side case), and the door pin inpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts. .changeset/22157-*.md.- Read, not edited:
packages/objectql/src/validation/rule-validator.ts(evaluateOptionVisibility), the binding the verdict mirrors.
Container & model:S,mode:subagent,model: opus(--tier: no path-derived mandate; the default tier). An at-tier contract review is owed before enqueue: the claim narrows an accept set at both doors.
Clause-②: no (narrowing:os buildand the object save door refuse a select option'svisibleWhenthat readsparent, which the runtime's option check cannot bind)
Grade: AGENTS.md is the governing text: "(narrowing)is BREAKING". So the changeset isminorwith a BREAKING section, the remedy, and an ADR-0087not-requireddisposition, as 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's passes 1–4 were. Triage's "patch" wording yields to that rule.
Responsibility:packages/lint's option pass, this lane's code | none: no other door judges an option predicate's roots | any author of a select option throughos buildorPUT /api/v1/meta/object(Studio, REST, MCP); measured by 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 pass 3's dev (report6051428578)
Thread-read: 6052678360
Serial constraints cleared: - 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 has closed. Its pass 4 (
73a0a6bf1d) and PR fix(spec): the retirement sentence names--write: it applies the edits it can prove, you apply the rest #22142 (de70e97a23) are the last writers ofvalidate-expressions.tsonmain. - Of the 14 open PRs at this stamp, none touches
validate-expressions.ts, the protocol door test orrule-validator.ts.
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22157,
"status": "done",
"branch": "claude/issue-22157-option-visible-when-parent",
"pr": "#22268",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK",
"premise_still_valid": true,
"summary": "The option loop in runStackExpressionPasses (packages/lint/src/validate-expressions.ts) gains a root verdict, optionVisibleWhenRootIssue: an allowlist of exactly what evaluateOptionVisibility binds (record, previous, and the acting user as current_user plus its ADR-0068 aliases user, ctx, os), judged against the real SCOPE_ROOTS, so parent and every other declared-but-unbound root is refused at error, located at the option slot, with a message naming the option, the field and the root. The object save door runs the same pass, so one change covers both doors: the measured body is refused 422 INVALID_METADATA through the real saveMetaItem (publish), its draft promotion and a package draft publish, while a draft save is still allowed; the runtime is untouched. Changeset is minor with a BREAKING section, the remedy and ADR-0087 not-required (no-migration-prescription), because AGENTS.md says '(narrowing) is BREAKING' and that outranks triage's 'patch'. Corpus first: 0 shipped option predicates are refused (5 predicates, roots record x4 and current_user x1).",
"tests": "All at head 012e8d7 unless stated. (1) pnpm --filter @objectstack/lint test: 126 files, 5757 tests passed. (2) pnpm --filter @objectstack/lint typecheck: tsc --noEmit OK and check:test-typecheck OK. (3) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts: 119 passed. (4) pnpm --filter @objectstack/metadata-protocol typecheck: OK (tsc --listFiles includes the test file). (5) Base reading of the new protocol block against lint built at 73a0a6b: 2 failed, 2 passed ((a) the publish save resolved {success:true}; (d) build gave 0 findings) = H4 before. (6) Corpus (scratch harness, not committed): A = 112 .object.ts + 2 app-multi-package sub-stacks = 119 objects / 18 groups, B = composed example stacks = 33 objects / 5 groups; 5 option predicates (showcase_cascade), 0 option findings at build and door, raw and parsed shapes, at 73a0a6b and 48b9329; positive control parent body: build 0 / door 0 at base, 1 / 1 at head. (7) Ablation from committed 012e8d7 via scripts/ablation-replace.mjs WRAP + outer trap restore on the absolute path: anchor x1 to x0, blob 9781066c15f0 to 570d3d5df714; predicted then observed lint source suites 5 failed / 371 passed (build parent, generated SCOPE_ROOTS table, two-roots case, door LIT, door PARITY); lint rebuilt, ablation-dist-preflight marker in 4 built files; protocol file 4 failed / 115 passed ((a), both (b) refusals, (d)); restore blob == HEAD 9781066c15f0, git diff HEAD empty, rebuild + preflight --absent: marker gone from all 14 built files, tree clean; suites back to 376/376 and 119/119. (8) ESLint narrowed: 4 changed .ts files, --no-inline-config --format json: 4 files, 0 errors, 0 warnings; population from eslint.config.mjs (**/.{ts,...} minus build-dir ignores); config enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move; repo-wide pnpm lint is CI's.",
"mcp_calls": "0 — no MCP tool called",
"api_writes": "3 — each one 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, #22268, body read back identical, 11737 bytes); (2) label-write --assign os-litant = POST /repos//issues/22268/assignees (read back: assignees os-litant, labels size/m untouched); (3) post-stamped os-dev-report = POST /repos//issues/22157/comments. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"class: a+c · reach: os build / os validate and the object save door (saveMetaItem publish, PUT /api/v1/meta/object) accept a select option visibleWhen of os.org.id != '' , os.env == 'prod' or ctx.locale == 'en' with 0 findings at head 012e8d7, and the built evaluateValidationRules with an authenticated caller admits os.org.id != '' and ctx.locale == 'en' with reason predicate-fault (No such key: org / No such key: locale; os.env not run) · Seam: spec:SelectOptionSchema.visibleWhen → runtime:evaluateOptionVisibility (passes no org/env, so ctx and os carry only their user member) · same family as this card (option-predicate roots, here at member level): for the family's closing card, not a single-point card; the fix needs a member-level shape decision, so it is not an in-place fix · dedupe words: option visibleWhen os.org ctx member unbound fail-open · option predicate member-level root os.env · evaluateOptionVisibility no org env bound",
"carrier: none · noted, not filed — an option visibleWhen reading app keeps the bare-reference message, which prescribes the record-qualified rewrite that #13935 removed on the field-rule slots; still refused at error, prose only (in PR Acceptance notes)",
"carrier: none · noted, not filed — validate-expressions.ts never walks a picklist metadata type's own option visibleWhen; whether the server option check ever evaluates those predicates was not read (unmeasured observation, in PR Acceptance notes)"
],
"gates": {
"head": "012e8d7dbe",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack re-derived after the last commit: 63 commands, identical to the claim-time list (empty diff); changed lines 455 (+449/-6), 5 files",
"reconcile": "dispatch-gates --ran with exit codes: 63 derived, 62 run, 1 NOT-MEASURED (derived from recorded exit 3), 0 UNRUN",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 — PREREQUISITE NOT MET: 8 packages unrelated to this diff have no dist/ (studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis, service-knowledge); a closure build of them exceeded the foreground cap; CI builds the full tree"
],
"results": [
"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-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-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-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/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: 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 3",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: 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: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:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: 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:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
},
"line_budget": {
"changed_lines": 455,
"additions": 449,
"deletions": 6,
"files": 5,
"vs": "merge base 73a0a6b of origin/main",
"human_merge_threshold": 5000
},
"files_changed": [
".changeset/22157-option-visible-when-parent.md",
"packages/lint/src/runtime-gate.object-option-visibility-writes.test.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/lint/src/validate-expressions.ts",
"packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts"
],
"deviations": [
"Verdict width: the root verdict is an allowlist (H2, and the claim's 'a root verdict over the roots evaluateOptionVisibility binds'), so besides parent it refuses every other SCOPE_ROOTS member the option check does not bind (input, vars, trigger, data, features and the rest). The Clause-② line copied verbatim from the claim names parent only; the changeset's BREAKING section names the full set. The corpus measurement covers every root (0 hits). Nowhere-bound roots outside SCOPE_ROOTS (app) are deliberately left to the existing bare-reference check, so the #13935 blast-radius pin is unchanged.",
"Not measured: pnpm check:dual-build-cjs-loads (exit 3, prerequisite not met), see gates.not_measured.",
"Declared narrowing: @objectstack/metadata-protocol's full test suite was not run locally; run were its runtime-authoring-gate test file (119/119) and its typecheck. A repo-wide sweep found no other test fixture carrying an option visibleWhen outside the bound roots. CI runs the full suite.",
"Pre mode: .changeset/pre.json is present on origin/main (mode pre, tag next); the bump is still minor per the ruling, and check-changeset-no-major is green.",
"Commit trailers follow AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By, per AGENTS.md precedence; the pre-push hook accepted them.",
"The showcase dependency-closure build (for corpus B) outlived the 600 s tool timeout and was moved to the background by the harness; I waited on it, read its VERDICT (command-exit 0), and nothing it started is still running.",
"Cleanup: node_modules and the worktree ../objectstack-issue-22157 are removed right after this comment is posted (no --force); the final message confirms the removal."
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actions✅ ACCEPT on the diff: PR #22268 at
012e8d7dbe. Landing held for the at-tier contract review and CIdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T10:55Z · holder of claim6056732334; the review of record for the report6058210167.Checklist (read on GitHub and in the PR's own diff):
-
Form: draft, base
main, assigneeos-litant. Line 1 isFixes #22157, and line 2 is the claim'sClause-②line, verbatim. -
Surface: 5 files, +449 / −6, all inside the claim.
check-governed-merges: not governed. -
The source change, read in the diff:
optionVisibleWhenRootIssueinvalidate-expressions.tsis an allowlist of exactly the rootsevaluateOptionVisibilitybinds:recordandprevious;- the acting user, as
current_userand its ADR-0068 aliasesuser,ctxandos.
It is judged against the real
SCOPE_ROOTS(packages/formula/src/cel-engine.ts). The seat read both:rule-validator.tspassesrecord,previous,userand thepermissionssource forcurrent_user.can;permissionsis not aSCOPE_ROOTSmember, so the allowlist misses nothing bound.
An unparseable source returns no verdict here, and the other checks own it.
-
The verdict's width (deviation 1), accepted. Beyond
parentit refuses every otherSCOPE_ROOTSmember the option check does not bind (input,vars,trigger,data,features, …). That is the claim's own "root verdict over the rootsevaluateOptionVisibilitybinds". The changeset's BREAKING section names the full set, and the corpus measurement covers every root, with 0 hits. Line 2's parenthetical namesparentonly, so the contract review is asked to judge whether it under-describes the narrowing. -
Both doors, one pass: the save door runs the same pass (since pass 3). The measured body is refused
422 INVALID_METADATAthroughsaveMetaItempublish, the draft promotion and the package draft publish, and a draft save is still allowed. The runtime is untouched, as ruled. -
Corpus: 0 shipped option predicates are refused (5 predicates:
record×4,current_user×1). The positive controlparentgives 0 / 0 at base and 1 / 1 at head. -
Reverse verification: with the verdict ablated, 5 lint and 4 protocol tests go red, as predicted. Restore was proved by blob equality.
-
Grade:
minorwith a BREAKING section, the remedy and the ADR-0087not-requireddisposition. AGENTS.md says "(narrowing)is BREAKING", which outranks triage's "patch", as the claim records. -
Gates: 63 derived, 62 run, 1 NOT-MEASURED. That one is
check:dual-build-cjs-loads, exit 3: a prerequisite, 8 unrelated packages without adist. CI builds the full tree.
Out of scope:
- Finding 1 is filed as lint: a select option's
visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274: an option predicate reading an unbound MEMBER of a bound root (os.org.id,os.env,ctx.locale) passes both doors and faults open. - Findings 2 and 3 are noted, with no carrier, in the PR's Acceptance notes:
- an option
visibleWhenreadingappkeeps the record-qualified prescription; - a picklist metadata type's own option predicates are not walked.
- an option
-
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22157,
"round": "fix round: PR #22268 Test Core (5/6) red",
"status": "done",
"branch": "claude/issue-22157-option-visible-when-parent",
"pr": "#22268",
"head": "012e8d7dbe (unchanged; nothing pushed this round)",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK",
"premise_still_valid": true,
"summary": "The red Test Core (5/6) job is a load-only timeout in a main-owned test that this PR's code never runs, so there is no PR-side fix and nothing was pushed. The failing test is packages/lint/src/build-access-matrix.test.ts, 'exhaustively matches the fold over every declared bit combination'. Per the contract reviewer's reading of the CI log, it timed out at 5000 ms, and no assertion failed. Two measurements settle it. The test's import closure (build-access-matrix.ts, object-graph.ts, system-fields.ts and the @objectstack/spec subpaths) never reaches validate-expressions.ts, and an instrumented run counted 0 calls of optionVisibleWhenRootIssue inside the test (positive control: 57 calls in the option door suite). Interleaved timings on identical dependencies give a median of 700 ms with the PR's validate-expressions.ts and 862 ms with the base version, so the PR adds no time. The suspected seam 326a90a does not touch that test's closure. On the merge with origin/main 8cbe255 (the same main the CI merge used), the full lint suite and the protocol door file pass.",
"failing_test": {
"file": "packages/lint/src/build-access-matrix.test.ts",
"name": "exhaustively matches the fold over every declared bit combination",
"verdict": "timeout at 5000 ms, no assertion failure. Read from the CI log by the contract reviewer (6058545425). This container cannot download the log: the check-run annotations carry only 'pnpm run test exited (1)'.",
"touched_by_this_pr": false
},
"root_cause": "Cause (2): a load-only timeout. Cause (1) is ruled out by construction and by measurement.\n- Construction: the test file and its in-package closure are byte-identical at 73a0a6b, 012e8d7 and the merge 4b664100d5. Blobs: test 85bbdd531123, build-access-matrix.ts 41e33458a1f0, object-graph.ts 693c48ac1308, system-fields.ts 3a07a37ef35f, vitest.config.ts 5e0591efc448. None of them imports validate-expressions.ts, and vitest isolates per file.\n- Count: a temporary console.log marker at the top of optionVisibleWhenRootIssue's root collection, placed with ablation-replace WRAP and restored to blob 9781066c15f0, printed 0 times in build-access-matrix.test.ts and 57 times in runtime-gate.object-option-visibility-writes.test.ts.\n- Timing: 7 interleaved pairs on the merged tree, same dependencies, swapping only validate-expressions.ts between HEAD (9781066c15f0) and 73a0a6b (62ee3cc8aece). Test duration: PR 598, 601, 616, 700, 757, 811, 836 ms, median 700. Base 768, 793, 848, 862, 887, 1007, 1015 ms, median 862. The gap is noise, because the file is not loaded at all. Load average was 2.25 to 3.44 on 4 cores.\n- The test itself is CPU-heavy: about 0.6 to 1.0 s on this box. For each of the 3^8 = 6561 entries it calls m.entries.find(), and the predicate builds a template string per comparison, about 21.5M string constructions. Under CI shard contention it reached its 5000 ms budget. It belongs to main, and per the ruling it is left untouched: not skipped, not loosened, timeout not raised.",
"fix": "None on the PR side: there is nothing to fix. A re-run of the job, or the next push, re-measures it. That is the seat's call for a load-only timeout.",
"changes_beyond_merge": "None. No file changed. I merged origin/main 8cbe255 through os-regen-merge.sh (merge 4b664100d5) only for the measurements and did not push it, because no PR-side change came out of the round. The local branch was then reset to the pushed head 012e8d7 so that no unpushed commit is left on the shared ref. The contract review of 012e8d7 still covers the PR head.",
"tests": "On the merge 4b664100d5 (the CI-equivalent tree, origin/main 8cbe255):\n- pnpm --filter @objectstack/lint test: 127 files, 5806 tests passed.\n- pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts: 119 passed.\n- build-access-matrix.test.ts: 14 timed runs, all passed, 598 to 1015 ms.\nNOT MEASURED: a full lint run without lint's own dist and with CI=true OS_TEST_TIERS=queue (the CI-like reproduction). It never acquired the verify lock. The same holds for the three-tree timing run (base and PR head in their own worktree with their own spec build): two queue-timeouts of 540 s each, with the holder a single spec test file held over 800 s. Declared narrowing: the timing and the call count ran WITHOUT the lock as light single-file runs, interleaved A/B on one tree so that contention hits both arms, and the comparison worktree's own spec build was dropped. That substitution holds because spec, formula and sdui-parser are byte-identical between 73a0a6b and 012e8d7 (diff: 0 lines), and the test's lint closure is identical at all three commits.",
"mcp_calls": "0 — no MCP tool called",
"api_writes": "1 this round — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay, via post-stamped) executing POST /repos//issues/22157/comments for this os-dev-report. No git push this round. Reads only besides: check-runs, the check-run annotations, the actions run and job.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: seat (load-only timeout handling) · noted, not filed — main's build-access-matrix.test.ts exhaustive-fold test is CPU-heavy: 6561 linear find() calls, each comparison building a template string, about 0.6 to 1.0 s locally, and it hit its 5000 ms budget under CI load on PR #22268's Test Core (5/6). Not one of the three filing classes; recorded for the seat's flake handling, with no change made."
],
"gates": {
"head": "4b664100d5 (merge with origin/main 8cbe255; the CI-equivalent tree, not pushed)",
"derivation": "dispatch-gates --commands --repo objectstack-ai/objectstack on 4b664100d5: 63 commands, identical to the claim-time and first-round lists; change set 5 paths, 455 lines (+449/-6) vs merge base 8cbe255",
"reconcile": "dispatch-gates --ran with exit codes: 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN",
"notes": "pnpm check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET: no packages/objectql/dist in the fresh worktree); after building @objectstack/objectql... under the lock it re-ran exit 0 (2 published conditions measured, admitted set held) and is recorded as exit 0",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 — PREREQUISITE NOT MET (some 60+ packages without dist/ in a fresh worktree); CI builds the full tree"
],
"results": [
"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-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-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-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/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: 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 3",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: 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: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:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: 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:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
},
"line_budget": {
"changed_lines": 455,
"additions": 449,
"deletions": 6,
"files": 5,
"vs": "merge base 8cbe255 of origin/main (unchanged from round 1 at 73a0a6b)"
},
"files_changed": [
".changeset/22157-option-visible-when-parent.md",
"packages/lint/src/runtime-gate.object-option-visibility-writes.test.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/lint/src/validate-expressions.ts",
"packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts"
],
"deviations": [
"No push. The coordinator's first message said to push (fast-forward) and its later message said to push only if something changed. The later one was followed: there is no PR-side change, so the merge 4b664100d5 was not pushed, and the local branch was reset to 012e8d7.",
"Declared narrowing: the timing and call-count runs went outside the verify lock after about 30 minutes of queue-timeouts behind a spec test holder. They were interleaved A/B single-file runs, with the substitution argument given in tests.",
"NOT MEASURED: the CI-like full lint run (no own dist, CI=true, OS_TEST_TIERS=queue) never acquired the lock. The CI log reading supplied by the coordinator, plus the measurements above, identify the cause without it.",
"Cleanup: ../objectstack-issue-22157 and the comparison worktree ../objectstack-issue-22157-cmp are removed right after this comment is posted (node_modules first, git worktree remove without --force); the final message confirms the removal."
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22157,
"round": "merge-of-main round on PR #22268 (no new work)",
"status": "done",
"branch": "claude/issue-22157-option-visible-when-parent",
"pr": "#22268",
"head": "c6d1020b90 (merge of origin/main b460153 into 012e8d7; pushed fast-forward 012e8d7..c6d1020)",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK",
"premise_still_valid": true,
"summary": "Merged origin/main b460153 (which carries #22358, the access-matrix fold fix) into the branch with scripts/pm/os-regen-merge.sh. The result is one merge commit, c6d1020, with no conflict, no os-regen path taken and no regeneration owed. The PR's delta against main is byte-identical to before the merge: for each of the 5 files, git patch-id --stable of the diff from the merge base (73a0a6b before, b460153 now) is equal. The full lint suite, lint typecheck, the protocol door file, metadata-protocol typecheck and the three changeset gates all pass at c6d1020, and the re-derived gate list is the same 63 commands as at claim time. On GitHub, Test Core (5/6) is now success at c6d1020.",
"merge": {
"method": "bash scripts/pm/os-regen-merge.sh (steps 1 to 3): git merge origin/main, then the merge commit. No rebase, no force-push.",
"conflicts": "none. git merge-tree before the merge gave exit 0. Step 2 took no os-regen path, because the branch edits none. os-regen-pending was absent after the merge, and the os-regen-merge-base record reads phase=done.",
"commit": "c6d1020b90, parents 012e8d7 and b460153. Before the push, the merge message was amended locally to add the model-free trailer pair (Claude-Session, Co-authored-by). The tree is unchanged: the tree id is equal before and after the amend.",
"incoming": "65 commits on main since 73a0a6b, 571 paths",
"regeneration": "none owed: the branch edits no generated artifact and packages/spec is not in the PR's delta. Step 4 of the script had nothing to regenerate."
},
"semantic_coupling": "No coupling. The main-side change in validate-expressions.ts (0e9e7b7, #19939 C half) has 3 hunks: an import swap, the deletion of the template-hint helpers, and the flow value-slot loop. All 3 are in the flow pass. The option loop, optionVisibleWhenRootIssue and OPTION_VISIBLE_WHEN_BOUND_ROOTS are byte-identical in the PR delta (equal patch-id). The sources the verdict mirrors are unchanged on main since the base: packages/formula/src (SCOPE_ROOTS, buildScope) and packages/objectql/src/validation/rule-validator.ts (evaluateOptionVisibility) have empty diffs from 73a0a6b to b460153. So the roots an option visibleWhen may read, and how the verdict is reached, are unchanged.",
"dispatch_premise_note": "The order says main landed two changes in validate-expressions.ts, #22319 (2f70c22) and #19939 C (0e9e7b7). Read on disk, 2f70c22 changes validate-expressions.ts by 0 lines. It changes validate-expressions.test.ts by 1 line: a flow loop fixture, itemVariable to iteratorVariable, inside the #17493 blank-predicate describe, away from this PR's #22157 describe. Only 0e9e7b7 changes the source file. Both are judged in semantic_coupling.",
"tests": "All runs are at c6d1020, after the dependency closure was built under the lock (turbo build of lint^... and metadata-protocol^...: 12 tasks, VERDICT command-exit 0). Each exit code was captured before any pipe, and each verify-lock run printed VERDICT command-exit 0.\n- pnpm --filter @objectstack/lint test: 128 files, 5862 tests passed (exit 0).\n- pnpm --filter @objectstack/lint typecheck: tsc --noEmit OK. check:test-typecheck OK: 2 files, 6 errors, 2 pinned signatures held in the debt ledger (exit 0).\n- pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts: 119 passed (exit 0).\n- pnpm --filter @objectstack/metadata-protocol typecheck: exit 0. Its tsconfig includes src/**/* and excludes only node_modules and dist, so the door test is in the program.\n- node scripts/check-changeset-no-major.mjs --base origin/main: exit 0, no major bump.\n- node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0. It reads the changeset as [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription).\n- node scripts/check-empty-changeset.mjs --base origin/main: exit 0.\n- The test behind the round: src/build-access-matrix.test.ts 'exhaustively matches the fold over every declared bit combination' passed in 28 ms at c6d1020 (single file, 8 of 8). The previous round measured 598 to 1015 ms before main's fix.\n- ESLint, narrowed to the 4 changed .ts files with --no-inline-config --format json: 4 files, 0 errors, 0 warnings. The population read from eslint.config.mjs is **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus the build-dir ignores. eslint.config.mjs is unchanged on main since 73a0a6b. It enables no type-aware linting (no parserOptions.project), so this diff cannot move the verdict on any file it does not touch.\n- Ablation: none this round. The PR delta is byte-identical, so the round-1 reverse verification (5 lint and 4 protocol tests red, restore proved by blob equality) still covers it.",
"ci_at_push": "One read of the check-runs at c6d1020 after the local runs: 33 runs, 32 completed with no failure (skipped: Build Docs, Console Pin Gate, Packed-tarball smoke), and Test Core (1/6) in_progress. Test Core (5/6), the job that was red on 012e8d7, is success. Lint & Repo Gates, TypeScript Type Check, Build Core, Dogfood Regression Gate, Temporal Conformance and Governed Surface Queue Guard are success. Not waited on further.",
"mcp_calls": "0 — no MCP tool called",
"api_writes": "1 REST write: POST /repos//issues/22157/comments for this os-dev-report, through scripts/pm/post-stamped.mjs (fleet-write relay). Also 1 git push (not a REST write): 012e8d7..c6d1020 on claude/issue-22157-option-visible-when-parent. No PR body write, no label write, no assignee write. Reads only besides: the card and its comments, PR #22268 and its comments, the check-runs at c6d1020.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": {
"head": "c6d1020b90",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c6d1020: 63 commands, identical line for line to the claim-time and previous-round lists. The change set is 5 paths, 455 lines (+449/-6), against merge base b460153. So the merge adds no derived family.",
"added_by_merge": "none. Of the 63, the gates whose judge or ledger moved on main in the merge window are check:durability-log-level (its script), check:engine-double-contract (its pinned ledger), check-platform-object-tenancy-census (its census JSON) and the docs-audit pair (handwritten-docs.json). All of them ran and exited 0. All 63 were run regardless.",
"reconcile": "dispatch-gates --ran with exit codes: 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN (recon exit 0)",
"notes": "pnpm check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET: no packages/objectql/dist). After turbo build --filter=@objectstack/objectql under the lock (14 of 14 tasks cached, VERDICT exit 0), it re-ran with exit 0: 2 published conditions measured, admitted set of 15 packages held. It is recorded as exit 0.",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3. PREREQUISITE NOT MET: 68 packages without dist/ in this worktree (8 named, then '… 60 more'), none of them in this diff. CI builds the full tree."
],
"results": [
"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-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-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-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/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: 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 3",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: 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: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:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: 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:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
},
"line_budget": {
"changed_lines": 455,
"additions": 449,
"deletions": 6,
"files": 5,
"vs": "merge base b460153 of origin/main (was 73a0a6b). Same counts as before the merge, and the delta is byte-identical by patch-id."
},
"files_changed": [
".changeset/22157-option-visible-when-parent.md",
"packages/lint/src/runtime-gate.object-option-visibility-writes.test.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/lint/src/validate-expressions.ts",
"packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts"
],
"pr_body_replacement": {
"why": "The body's Local verification section is stamped at 012e8d7, and its lint counts (126 files, 5757 tests) are that head's. At c6d1020 the counts are 128 files and 5862 tests. The rest of the body is unchanged by the merge. The seat writes it: replace the whole section, from its heading down to just before the Grade and changeset heading, with the text below.",
"section": "## Local verification (atc6d1020b90, the merge oforigin/mainb460153912)\n\n- The merge changed none of this PR's lines. For each of the 5 files,git patch-id --stableof the diff from the merge base is equal at012e8d7dbe(base73a0a6bf1d) and atc6d1020b90(baseb460153912).\n-pnpm --filter @objectstack/lint test: 128 files, 5862 tests passed.\n-pnpm --filter @objectstack/lint typecheck:tsc --noEmitandcheck:test-typecheckboth OK.\n-pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts: 119 passed.\n-pnpm --filter @objectstack/metadata-protocol typecheck: OK. Itstsconfig.jsonincludessrc/**/*and excludes onlynode_modulesanddist, so the test file is in the program.\n- Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatc6d1020b90derived the same 63 commands as at claim time. 62 exited 0.pnpm check:dual-build-cjs-loadsexited 3 with PREREQUISITE NOT MET: 68 packages unrelated to this diff have nodist/in the local worktree. NOT MEASURED: dual-build-cjs-loads, reason: prerequisite not met locally; CI builds the full tree. The--ranreconciliation reads 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN.\n- ESLint, narrowed to the 4 changed.tsfiles with--no-inline-config --format json, gave 4 files, 0 errors and 0 warnings. The population was read fromeslint.config.mjs:**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minus the build-dir ignores. That config enables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on any file it does not touch. The repo-widepnpm lintis CI's."
},
"deviations": [
"Push timing: the order lists the push as step 4. I pushed the merge commit right after the semantic read and before the build and test runs, as AGENTS.md requires ('Push a WIP commit before every step that takes minutes'). It was one fast-forward push, 012e8d7..c6d1020, and nothing was pushed after it, so the pushed head is the one measured.",
"Merge message amended before the push: the os-regen-merge.sh merge commit (10c987f081) carried git's default message only. It was amended locally to add the model-free trailer pair and one line of body prose. Parents and tree are unchanged (tree id compared). It was never pushed in its first form, so no pushed history was rewritten. The pre-push check:commit-card-trailers passed: no card relation and no model identifier.",
"Gate scope: the order asks for what the merge ADDS that reads this PR's files, which is zero (identical derivation). I ran all 63 anyway, so the --ran reconciliation is complete at the new head.",
"NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, prerequisite not met locally: 68 packages without dist/). CI builds the full tree.",
"Cleanup: ../objectstack-issue-22157 is removed after this comment is posted (node_modules first, then git worktree remove without --force). The final message confirms the removal."
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actions✅ ACCEPT on the delta: PR #22268 at
c6d1020b90(a merge ofmaincarrying the timeout fix). The PR's own diff is unchangeddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T23:50Z · holder of claim6056732334; the review of record for the merge-round report6071296830. The ACCEPT on012e8d7dbestands for the change itself. The contract review PASS6058545425names012e8d7dbe; a delta review is commissioned.- Why the round:
Test Core (5/6)had been red on012e8d7dbesince 11:21Z with the lint access-matrix timeout (6059966906).mainnow carries its fix: PR test(lint): index the access-matrix rows once in the exhaustive fold case #22358 →b460153912(hourly full run: red on main (CI) #22346). A merge that carries the fix re-runs CI, which this seat's write channel cannot do another way. - The delta, read by the seat:
- One merge commit
c6d1020b90(parents012e8d7dbe,b460153912), with no conflict and nothing to regenerate. - The PR's delta is byte-identical:
git patch-id --stableper file is equal across the two bases.
- One merge commit
- Semantic coupling, checked:
main's [v18] retire the{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939 C half (0e9e7b7066) changedvalidate-expressions.tsonly in the flow pass: an import swap, deleting the template-hint helpers, and the value-slot loop.- The option loop,
optionVisibleWhenRootIssueandOPTION_VISIBLE_WHEN_BOUND_ROOTSare untouched. - The sources the verdict mirrors (
packages/formulaSCOPE_ROOTS,rule-validator.tsevaluateOptionVisibility) have empty diffs across the window. - fix(spec,service-automation)!: an undeclared config key on 10 more builtin node types is refused at the build doors; one judge per type #22319 (
2f70c2222d) touched only one fixture line in the test file, in another describe. (The seat's order said it changed the source file; it did not.)
- Verified at the head:
@objectstack/lintfull test (128 files, 5,862 tests) and its typecheck;- the protocol door file (119);
metadata-protocoltypecheck;- the three changeset gates;
- all 63 derived gate families, with 62 run and
check:dual-build-cjs-loadsNOT MEASURED (a prerequisite; CI builds the tree). - The access-matrix case now runs in 28 ms.
- CI:
Test Core (5/6)is success onc6d1020b90. The rest is the verdict as it completes.
- Why the round:
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22268 →
bb4f5cc005. This card closescompleteddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-09T00:43Z · holder of claim6056732334.- Landing: PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268 merged through the merge queue at 2026-10-09T00:43Z as
bb4f5cc005, which has one parent (345d3f3d86) and is an ancestor oforigin/main. ItsFixes #22157closed the card. - Content check: all 5 files are blob-equal to the reviewed head
c6d1020b90. - Review of record:
- ACCEPT
6058329084and its delta6071326077; - the at-tier contract review PASS
6058545425, and the delta PASS6071451400onc6d1020b90; - CI green there, with 5 skips, all in the roster.
- ACCEPT
- The red that held it from 11:21Z to 23:50Z: a timeout in
packages/lint/src/build-access-matrix.test.ts, which was not this PR's. hourly full run: red on main (CI) #22346 / PR test(lint): index the access-matrix rows once in the exhaustive fold case #22358 (b460153912) fixed it onmain, and this PR mergedmainto carry the fix. - What now holds:
os buildand the object save door refuse a select option'svisibleWhenthat reads a root the option check does not bind (parent, and every otherSCOPE_ROOTSmember outsiderecord,previousand the acting-user roots). The refusal names the root and the bound set. The runtime is unchanged.- It ships
minorfor@objectstack/lintwith its BREAKING section and remedy.
Carried elsewhere:
- lint: a select option's
visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274: an option predicate reading an unbound MEMBER of a bound root (os.org.id,os.env,ctx.locale). - The other two Acceptance notes are in the PR body, with no carrier.
- Landing: PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268 merged through the merge queue at 2026-10-09T00:43Z as
- added 3 commits that reference this issue
on Oct 9, 2026
Filing gate: ① a reproducible defect, class (c) with class-(a) evidence. A declared gate the runtime cannot enforce passes every build door, measured through the real save door and the real option check. Found by #22032 pass 3's dev (PR #22151, report
6051428578,out_of_scope_findings[0]) and filed by thedomain:specseat 3 (seat post #18883,session_01RPo7FUd6bSnAfkWMAKi848). ⛔ Not graded or routed here; ⛔ not a claim.What is measured (by the dev, at PR #22151's pre-merge head
23ce6c494c, with scratch tests since deleted)The doors accept it. Through the real
saveMetaItemin publish mode, an object whose select option carriesvisibleWhen: "parent.status == 'closed'"saved with success, and the row landedactive.runAuthoringRules('build')gave 0 findings on the same body, soos buildaccepts it too. With PR fix(lint)!: the object save door gives the build's option visibleWhen verdict (#22032 pass 3) #22151 the save door gives the build's verdict, so it now mirrors the build's silence.The runtime cannot enforce it. The server's option check (
evaluateValidationRules, with an authenticatedcurrentUserand that option picked) did not throw. It logged:The reason was
predicate-fault,Unknown variable: parent.Where it is (read in source by the dev)
evaluateOptionVisibility(packages/objectql/src/validation/rule-validator.ts) evaluates an option predicate with{ record, previous, user, permissions }only. It binds noparent.runStackExpressionPasses(packages/lint/src/validate-expressions.ts) has no root verdict.fieldRuleRootVerdictserves the field-rule slots only.parentis a declared root of the strict env, so the bare-reference check does not fire either.Seam:
spec:SelectOptionSchema.visibleWhen→runtime:evaluateOptionVisibility(bindsrecord,previous,user,permissions).Why it matters
An option
visibleWhenis a server-enforced gate on which value a caller may pick. An author (often an AI) writesparent.…, which the field-rule slots one level up accept on an object with exactly onemaster_detail. The build stays green and the door stores it. At every write the gate is silently not enforced. The predicate is declared and never kept, and the failure is quiet.Reader who acts
Triage grades and routes it. Candidate landings, for triage to choose:
evaluateOptionVisibilitybinds, refusingparentwith its location. That is the narrowing direction, and the corpus is measured first: today's 5 in-tree option predicates readrecordandcurrent_useronly (measured by the same dev).parentfor options the way the field-rule slots do.Related class, closed: #12915 (unbound roots fault open on form-view predicates).
Dedupe
MCP
search_issues, repo-scoped, closed included:required: truedead-ends console record creation — no ADR-0087 conversion covers the predicate-root move #12915 (closed; form-view predicate roots, a different slot). None is this.Dedupe words:
option visibleWhen parent root unbound fail-open·select option visibleWhen reads parent os build accepts·option predicate root verdict·evaluateOptionVisibility unknown variable parentGenerated by Claude Code