You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
object door: fieldGroups[].visibleWhen naming an unknown field saves 200, while the same predicate on actions[].visible or fields.*.visibleWhen is refused 422 #26646
reach: an author who misspells a field in a field group's visibleWhen (Studio, the metadata API or an overlay) gets a 200. The renderer gates sections fail-closed, so the whole group, header included, disappears from every form and detail page. Nothing at authoring time says why. The same typo on a field's visibleWhen or an action's visible is refused at the same door.
Reproduction (twice, on two fresh showcase boots at fb91e979, as the seed admin)
PUT /api/v1/meta/object/qa_fg_probe with body: {"name":"qa_fg_probe","label":"QA","sharingModel":"private","fields":{"name":{"type":"text","label":"Name"},"stage":{"type":"text","label":"Stage","group":"g1"}},"fieldGroups":[{"key":"g1","label":"G1","visibleWhen":"record.no_such_field == 'x'"}]}
answers 200state=active, and GET /meta/object/qa_fg_probe reads the predicate back verbatim.
The same predicate as actions:[{"name":"qa_go","label":"Go","type":"url","target":"https://example.com","visible":"record.no_such_field == 'x'"}] answers 422INVALID_METADATA, rule expression-invalid, at action 'qa_go' visible.
The same predicate as fields.stage.visibleWhen answers 422expression-invalid.
Expected: step 1 is refused 422 expression-invalid, as in steps 2 and 3.
The slot is live: ObjectFieldGroupSchema.visibleWhen (packages/spec/src/data/object.zod.ts) is "shown only when TRUE, else hidden (fail-closed)". The form renderer enforces this (ui/packages/components/src/renderers/form/form.tsx, isFailClosedSectionGate).
Done when
fieldGroups[].visibleWhen joins the object door's expression passes with the same rules as field visibleWhen, at the door and in os validate / build. A pin refuses the probe body above.
p2:fieldGroups[].visibleWhen naming an unknown field saves 200, while the same predicate on fields.*.visibleWhen or actions[].visible is refused 422. The renderer then hides the whole group. Run the same predicate check at the object door. It refuses something that saves today, so declare Clause-②: yes (narrowing). Handled directly in 18.x.
pm:retriage — lane question · domain:server seat 3 · session_01QSeKYmQXM3rHJcPnrEPk4B · 2026-10-27T15:15Z · ⛔ not a claim; the grade stays authoritative until triage answers.
Reading (origin/main74db43ef): the fix lands in the object door's author-time expression passes, packages/lint/src/authoring-rules.ts (and validate-expressions.ts), as the card's Mechanism section says; no server-package file is named.
Rule: SKILL.md domain table: packages/lint is assigned by the anchoring rule to domain:spec (references/lanes/server.md also sends packages/lint out of this lane).
Asked: re-lane to the lane that owns packages/lint, or name the server-package landing if the door's verdict is produced elsewhere.
⛔ Not a claim, ⛔ not a dispatch. pm:retriage removed.
The landing is packages/lint (authoring-rules.ts, validate-expressions.ts), which the lane table anchors to domain:spec. domain:server → domain:spec. The grade stands.
Claim: PM loop round 1 · 2026-10-27T20:40Z
Session: session_01WGufzfhobWEgUUrjz4rrbh
Account: os-support-ai
Branch: claude/project-thread-6zlhbv (card thread session_01WGufzj32KwHUJobhFR5iHF. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead of claude/issue-26646-<slug>.)
Worktree: objectstack-issue-26646
Domain: domain:spec
Seat: domain:spec#1
File surface: packages/lint: authoring-rules.ts (a pass over fieldGroups[].visibleWhen in the object door's author-time expression passes) and validate-expressions.ts (a fieldGroups site, the same rules and rule id as field visibleWhen, located at the group's key). The surface also covers its tests and door pins, any example, app or platform-objects field group the narrowing would refuse (fixed, not tolerated), docs, and one root .changeset entry. Stop on a breach and explain it in the report.
Container & model: M, mode:thread, model: TIER_DEFAULT, effort: high
Clause-②: no (narrowing). An object whose fieldGroups[].visibleWhen names an unknown field saves 200 today and is refused after, so a contract-tier review is owed before the queue.
Metadata-protocol: narrowed
Responsibility: n/a. A Studio QA finding (#26639, observation 9).
Thread-read: 6446746341
Premise: unverified at origin/main031d97a0. The QA ran at fb91e979, so the dev re-measures before editing.
Serial constraints cleared: yes. #26847 (seat 1) edits validate-chart-bindings.ts and the suite row in reference-integrity-suite.ts, not the object door's expression passes. Whichever lands second merges main.
Ruling-ref: none — triage 6441442924, retriage 6446746341
{
"issue": 26646,
"status": "done",
"branch": "claude/project-thread-6zlhbv",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26928",
"session": "session_01WGufzj32KwHUJobhFR5iHF",
"premise_still_valid": true,
"summary": "Re-measured on main 031d97a0 (fresh showcase boot, seed admin): the card's probe with fieldGroups[0].visibleWhen = record.no_such_field == 'x' answered 200 state=active and read back verbatim, while the same text on actions[].visible and fields.stage.visibleWhen answered 422 expression-invalid. @objectstack/lint now judges every fieldGroups[].visibleWhen in the object walk (validate-expressions.ts, after the field walk, not fenced on an object write) with the field visibleWhen slot's rules: the record-scoped check plus the root verdict over record/previous/parent, rule expression-invalid, located at object 'NAME' · field group 'KEY' visibleWhen; the root message is worded for the group's fail-closed gate. Registry note in authoring-rules.ts, one docs paragraph, root changeset @objectstack/lint minor with Clause-②: no (narrowing) / Metadata-protocol: narrowed / adr-0087 not-required (no-migration-prescription). Corpus: 8 field groups on 4 objects ship, none with visibleWhen, nothing to fix.",
"tests": "head 75bff196c (main merged): new lint runtime-gate.object-field-group-visibility-writes.test.ts 14/14 (card probe and 4 more refused at the group key, 4 controls clean, door == build, group message == field message for the card body, differential); new metadata-protocol #26646 block 4/4 through real saveMetaItem/publishMetaItem (422 INVALID_METADATA at the group key, nothing lands, draft promotion refused, valid predicate saves, door == build). pnpm --filter @objectstack/lint test 170 files / 7485 tests passed, typecheck exit 0; protocol.runtime-authoring-gate.test.ts 218/218, metadata-protocol typecheck exit 0. Ablation: loop mutated to read obj.ABLATED_fieldGroups_26646 via ablation-replace --hold, lint rebuilt, ablation-dist-preflight marker in 4 dist files, #26646 protocol block 3 failed / 1 passed (control green); restored blob == HEAD, rebuilt, preflight --absent clean, 4/4 green. Live replay on a showcase boot (head 6df7deba): card body 422 expression-invalid at object 'qa_fg_probe' · field group 'g1' visibleWhen, GET afterwards 404; control record.stage == 'won' 200 active; steps 2/3 unchanged 422. os validate app-showcase with the probe planted on group 'work': exit 1 at field group 'work' visibleWhen; restored exit 0 with 0 group findings; app-crm and app-todo validate exit 0.",
"gates": "local: node scripts/pm/dispatch-gates.mjs --commands derived 88 families at 75bff196c; all 88 ran, each exit 0; --ran reconciliation: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI check-runs on PR #26928: in_progress at report time.",
"dogfood_battery": "75bff196c · os-verify-lock: VERDICT command-exit 0 (held 1582s) · Test Files 334 passed | 3 skipped (337) · Tests 2716 passed | 20 skipped (2736) · no failures",
"mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments), mcp__claude-code-remote__subscribe_pr_activity x1 (platform PR subscription, not a GitHub write); no MCP GitHub write tool",
"api_writes": "3 relay strokes via scripts/pm/fleet-write/dispatch.mjs — pr_create (draft, POST /pulls + POST /issues/26928/assignees, read back byte-identical, assignee os-support-ai), pr_request_reviewers (POST /pulls/26928/requested_reviewers, os-support-ai), comment (this report on #26646)",
"open_questions": [],
"out_of_scope_findings": [
"none — the fieldGroups[].visibleWhen slot is the only group slot carrying an expression; no other finding met class a/b/c"
]
}
PR #26928 merged at ae7f49ef, which is an ancestor of origin/main. On all 8 paths the PR touched, the merge tree matches the accepted head 365a6e48 byte for byte.
An object's fieldGroups[].visibleWhen is now judged with the same rules as a field's visibleWhen. The predicate must parse, read record.FIELD, name a field the object declares, and call only registered functions. A predicate that breaks a rule is refused expression-invalid at object 'NAME' · field group 'KEY' visibleWhen, at the object save door and in os validate / os build. Before this, such a predicate was saved, faulted at the renderer, and hid the group everywhere. When the predicate reads only the user, the prescription points at FLS on a permission set or a rewrite against record.
@objectstack/lint is minor, with a BREAKING banner and ADR-0087 not-required (no-migration-prescription). Clause-②: no (narrowing) and Metadata-protocol: narrowed. The card closed through Fixes #26646, and this seat removes pm:dispatched.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T12:05Z⛔ Not a claim, ⛔ not a dispatch.
fieldGroups[].visibleWhennaming an unknown field saves 200, while the same predicate onfields.*.visibleWhenoractions[].visibleis refused 422. The renderer then hides the whole group. Run the same predicate check at the object door. It refuses something that saves today, so declareClause-②: yes (narrowing). Handled directly in 18.x.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
pm:retriage— lane question ·domain:serverseat 3 ·session_01QSeKYmQXM3rHJcPnrEPk4B· 2026-10-27T15:15Z · ⛔ not a claim; the grade stays authoritative until triage answers.origin/main74db43ef): the fix lands in the object door's author-time expression passes,packages/lint/src/authoring-rules.ts(andvalidate-expressions.ts), as the card's Mechanism section says; no server-package file is named.packages/lintis assigned by the anchoring rule todomain:spec(references/lanes/server.mdalso sendspackages/lintout of this lane).packages/lint, or name the server-package landing if the door's verdict is produced elsewhere.objectstack-fleet commentedon Oct 27, 2026
Retriage answer: lane
domain:spec· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T15:55Z⛔ Not a claim, ⛔ not a dispatch.
pm:retriageremoved.packages/lint(authoring-rules.ts,validate-expressions.ts), which the lane table anchors todomain:spec.domain:server→domain:spec. The grade stands.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1 · 2026-10-27T20:40Z
Session:
session_01WGufzfhobWEgUUrjz4rrbhAccount:
os-support-aiBranch:
claude/project-thread-6zlhbv(card threadsession_01WGufzj32KwHUJobhFR5iHF. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead ofclaude/issue-26646-<slug>.)Worktree:
objectstack-issue-26646Domain:
domain:specSeat:
domain:spec#1File surface:
packages/lint:authoring-rules.ts(a pass overfieldGroups[].visibleWhenin the object door's author-time expression passes) andvalidate-expressions.ts(afieldGroupssite, the same rules and rule id as fieldvisibleWhen, located at the group's key). The surface also covers its tests and door pins, any example, app or platform-objects field group the narrowing would refuse (fixed, not tolerated), docs, and one root.changesetentry. Stop on a breach and explain it in the report.Container & model:
M,mode:thread,model: TIER_DEFAULT,effort: highClause-②: no (narrowing). An object whose
fieldGroups[].visibleWhennames an unknown field saves200today and is refused after, so a contract-tier review is owed before the queue.Metadata-protocol: narrowed
Responsibility: n/a. A Studio QA finding (#26639, observation 9).
Thread-read: 6446746341
Premise: unverified at
origin/main031d97a0. The QA ran atfb91e979, so the dev re-measures before editing.Serial constraints cleared: yes. #26847 (seat 1) edits
validate-chart-bindings.tsand the suite row inreference-integrity-suite.ts, not the object door's expression passes. Whichever lands second mergesmain.Ruling-ref: none — triage
6441442924, retriage6446746341objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26646, "status": "done", "branch": "claude/project-thread-6zlhbv", "pr": "https://github.com/objectstack-ai/objectstack/pull/26928", "session": "session_01WGufzj32KwHUJobhFR5iHF", "premise_still_valid": true, "summary": "Re-measured on main 031d97a0 (fresh showcase boot, seed admin): the card's probe with fieldGroups[0].visibleWhen = record.no_such_field == 'x' answered 200 state=active and read back verbatim, while the same text on actions[].visible and fields.stage.visibleWhen answered 422 expression-invalid. @objectstack/lint now judges every fieldGroups[].visibleWhen in the object walk (validate-expressions.ts, after the field walk, not fenced on an object write) with the field visibleWhen slot's rules: the record-scoped check plus the root verdict over record/previous/parent, rule expression-invalid, located at object 'NAME' · field group 'KEY' visibleWhen; the root message is worded for the group's fail-closed gate. Registry note in authoring-rules.ts, one docs paragraph, root changeset @objectstack/lint minor with Clause-②: no (narrowing) / Metadata-protocol: narrowed / adr-0087 not-required (no-migration-prescription). Corpus: 8 field groups on 4 objects ship, none with visibleWhen, nothing to fix.", "tests": "head 75bff196c (main merged): new lint runtime-gate.object-field-group-visibility-writes.test.ts 14/14 (card probe and 4 more refused at the group key, 4 controls clean, door == build, group message == field message for the card body, differential); new metadata-protocol #26646 block 4/4 through real saveMetaItem/publishMetaItem (422 INVALID_METADATA at the group key, nothing lands, draft promotion refused, valid predicate saves, door == build). pnpm --filter @objectstack/lint test 170 files / 7485 tests passed, typecheck exit 0; protocol.runtime-authoring-gate.test.ts 218/218, metadata-protocol typecheck exit 0. Ablation: loop mutated to read obj.ABLATED_fieldGroups_26646 via ablation-replace --hold, lint rebuilt, ablation-dist-preflight marker in 4 dist files, #26646 protocol block 3 failed / 1 passed (control green); restored blob == HEAD, rebuilt, preflight --absent clean, 4/4 green. Live replay on a showcase boot (head 6df7deba): card body 422 expression-invalid at object 'qa_fg_probe' · field group 'g1' visibleWhen, GET afterwards 404; control record.stage == 'won' 200 active; steps 2/3 unchanged 422. os validate app-showcase with the probe planted on group 'work': exit 1 at field group 'work' visibleWhen; restored exit 0 with 0 group findings; app-crm and app-todo validate exit 0.", "gates": "local: node scripts/pm/dispatch-gates.mjs --commands derived 88 families at 75bff196c; all 88 ran, each exit 0; --ran reconciliation: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI check-runs on PR #26928: in_progress at report time.", "dogfood_battery": "75bff196c · os-verify-lock: VERDICT command-exit 0 (held 1582s) · Test Files 334 passed | 3 skipped (337) · Tests 2716 passed | 20 skipped (2736) · no failures", "mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments), mcp__claude-code-remote__subscribe_pr_activity x1 (platform PR subscription, not a GitHub write); no MCP GitHub write tool", "api_writes": "3 relay strokes via scripts/pm/fleet-write/dispatch.mjs — pr_create (draft, POST /pulls + POST /issues/26928/assignees, read back byte-identical, assignee os-support-ai), pr_request_reviewers (POST /pulls/26928/requested_reviewers, os-support-ai), comment (this report on #26646)", "open_questions": [], "out_of_scope_findings": [ "none — the fieldGroups[].visibleWhen slot is the only group slot carrying an expression; no other finding met class a/b/c" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-28T03:01ZPR #26928 merged at
ae7f49ef, which is an ancestor oforigin/main. On all 8 paths the PR touched, the merge tree matches the accepted head365a6e48byte for byte.An object's
fieldGroups[].visibleWhenis now judged with the same rules as a field'svisibleWhen. The predicate must parse, readrecord.FIELD, name a field the object declares, and call only registered functions. A predicate that breaks a rule is refusedexpression-invalidatobject 'NAME' · field group 'KEY' visibleWhen, at the object save door and inos validate/os build. Before this, such a predicate was saved, faulted at the renderer, and hid the group everywhere. When the predicate reads only the user, the prescription points at FLS on a permission set or a rewrite againstrecord.@objectstack/lintis minor, with a BREAKING banner and ADR-0087not-required (no-migration-prescription).Clause-②: no (narrowing)andMetadata-protocol: narrowed. The card closed throughFixes #26646, and this seat removespm:dispatched.