Repository navigation
studio(cel editor): an entry condition using record.x shows "Valid CEL" and "record is not a reference in scope" at the same time #11789
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving it
on Oct 7, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsPath: ③ 验证:响亮拒绝错的,放行对的 — the CEL editor's verdict is one verdict | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:ui·area:workflow·pm:queue. Direction: the scope check downgrades the syntax verdict and suggests the in-scope spellingTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T16:19Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in Studio's CEL editor (the Start node's entry condition) ⇒
domain:ui; rationale: a Studio or console surface in objectui. Filed from the Studio browser QA pass of 2026-10-07 (objectstack879bd38c, objectui179f6fe9).- Why p3: the editor says "Valid CEL" and "not in scope" at once, which confuses but does not mis-save.
- Direction:
- the scope check downgrades the syntax verdict
- suggest the in-scope spelling (
statusfor a bare field in an entry condition)
- Related: cel: the record scope differs by surface — validation rules use record.status, flow entry conditions use bare status, notify templates use {record.assignee} objectstack#22096 asks whether the record scope should be one shape across surfaces. That is a decision question, and this card does not wait on it.
Clause-②: no. Patch changeset in objectui.
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsThis amends my
6042038047(one point: the direction). Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T17:13Z. ⛔ Not a claim.The grade told the editor to suggest the bare spelling (
status) in an entry condition. That was wrong:record.statusis valid there. Read on objectstackmainaa71c4d9d1:- The engine binds
recordfor a record-triggered run.seedRunVariablessets$record,recordandprevious, and spreads the record's fields (packages/services/service-automation/src/engine.ts:12462–12468). Both spellings resolve. - The server's validator accepts it: the
'flattened'scope registersrecordamongSCOPE_ROOTS(packages/formula/src/cel-engine.ts:131). - The canon writes it that way:
skills/objectstack-automation/SKILL.md:382usesrecord.status == 'escalated'in a flow condition.
Corrected direction:
- On a record-triggered flow, the scope Studio checks against includes
recordandprevious. Sorecord.status == 'done' && previous.status != 'done'reads only "Valid CEL". - Where it sits:
packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.ts(RUNTIME_GLOBALSat:38has neither root), or the scope groups its callers pass. - When a root really is out of scope, the scope verdict still replaces the syntax verdict, as graded.
objectstack-ai/objectstack#22096 is answered the same way and closed this round.
- The engine binds
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_01CGZy1BGCjdN5cXqL9cnvB8
Account:os-support-ai
Branch:claude/issue-11789-cel-scope-verdict
Worktree:objectui-issue-11789
Domain:domain:ui
Seat:domain:ui#3
File surface (on16cda0b), underpackages/app-shell/src/views/metadata-admin/:inspectors/flow-scope.ts(the start node's scope: the bare-field prefix at:74, andrecord/previousat:83);inspectors/flow-ref-check.ts(RUNTIME_GLOBALSat:38, the root check at:170);CelPredicateField.tsx(the "Valid CEL" verdict,perm.cel.valid) andinspectors/FlowExprIssue.tsx(the scope issue line), for the one-verdict rule;- the
perm.cel.*/ scope rows ofi18n.ts; - the tests beside them;
.changeset/11789-*.md.
⛔ Not on it: this seat's objectui#11827 files (
FlowNodeInspector.tsx,flow-problems.ts; in the merge queue) and seat 1's objectui#11783 files (FlowReferenceField.tsx,ObjectFieldInspector.tsx,flow-node-config.ts). Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)
Clause-②: no
Responsibility:objectui app-shell: on a record-triggered flow's Start node, the scope Studio checks against has neither record nor previous (flow-ref-check RUNTIME_GLOBALS, flow-scope's start-node roots), so a valid record.status == 'done' && previous.status != 'done' reads "Valid CEL" and "record is not a reference in scope" at once | the platform path: the engine binds record and previous for a record-triggered run (objectstack engine.ts seedRunVariables) and the server's 'flattened' CEL scope registers record (triage amendment 6042943640) | every author writing a record-triggered flow's entry condition with record. or previous.
Thread-read: 6042943640
Serial constraints cleared:noneblocking. This seat's objectui#11827 (area:workflow, in the merge queue) holdsFlowNodeInspector.tsxandflow-problems.ts, which are disjoint. No open objectui PR touches the files above (read 2026-10-07T23:54Z; open: #11837, #11836, #11600, #11069).Why
Clause-②: no: a scope and verdict fix inside@object-ui/app-shell. Its strings are rows of its unexported designer table. A published export, prop, type member or language-pack key is a stop: the dev reports it before opening the PR, and the seat amends this line.
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11789, "status": "blocked", "branch": "claude/issue-11789-cel-scope-verdict", "pr": null, "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent seat's id, per the brief)", "premise_still_valid": true, "blocked_on": "Claim 6049225787's File surface. The fix needs 2 source files outside it, plus 2 wording-only test edits. The claim says to stop before opening the PR on a breach, so no PR is open. The branch at 1bbfc4b holds the whole implementation, verified. Once the seat amends the surface (or drops the two wording-only test edits), the PR can be opened from 1bbfc4b as-is.", "summary": "Implemented and pushed to 1bbfc4b (one commit on base 87f7b6c). (1) flow-scope.ts: the whole `record` is now in scope on a record-triggered Start node as well as downstream. The engine's seedRunVariables binds record, $record, the flattened fields and previous before the start-condition gate, which evaluates against that same variables map. So `record.status == 'done' && previous.status != 'done'` reads only Valid CEL. A schedule, manual or API Start node gains neither root. (2) `previous` now follows the engine's pre-image. Update and before-update: the prior row. Create-or-update (write): null on the create leg, the prior row on the update leg. Delete (NEW in Studio's set): the deleted row, which the data engine binds before the delete and the record-change trigger reads `record` from. Create: excluded. The engine binds it there only as null, and evaluateCondition throws on a CEL evaluation error, so `previous.x` fails the run; the flag is the useful verdict. (3) One verdict: when the field's scope check names an out-of-scope root, the raw CEL editor withholds Valid CEL, and the scope note under the field is the only verdict. CelPredicateField gains an optional `scopeIssue`, forwarded by ConditionBuilder from FlowNodeConfigField. Side effect, matching the engine: an edge leaving the Start node reads the Start scope, so its guard accepts `record` too. The Problems panel skips the Start node and its out-edges, so it is unchanged. Zone 2 #3 location falsified: the scope line under the entry condition is FlowNodeConfigField's own describeUnknownRefs note, not FlowExprIssue. FlowExprIssue never renders beside a CelPredicateField, because only the start node's `condition` opts into conditionBuilder. flow-ref-check.ts is untouched: adding the roots to RUNTIME_GLOBALS would hand them to schedule flows. i18n.ts is untouched: no new strings.", "files_surface_breach": [ "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.tsx: REQUIRED. It computes and renders the Start node entry condition's scope note, and it mounts the ConditionBuilder. The scope verdict is now computed before the control and passed down as scopeIssue. Its FLOW_TRIGGER_CONTEXT_SUBJECTS doc comment said flow-scope withholds `record` on the start node, which this fix makes false, so it was reworded. The subject list stays empty: record.FIELD is the same value as the bare FIELD already offered.", "packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx: REQUIRED. It mounts CelPredicateField in raw mode. One optional prop (scopeIssue) is declared and forwarded; no other change.", "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.entryCondition.test.tsx: wording only (one test name and its comment said `record` is the root this site does not bind). DROPPABLE.", "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.entryCondition.test.tsx: wording only (one comment, same false reason). DROPPABLE." ], "clause_2": "no. Measured on the built package: walked dist/index.d.ts relative imports transitively (172 d.ts files). CelPredicateField, ConditionBuilder, FlowNodeConfigField, flow-scope, flow-ref-check, FlowExprIssue and useFlowScope are all NOT reachable. Positive control: DirectoryPage.d.ts reachable. scopeIssue appears in the emitted CelPredicateField.d.ts and ConditionBuilder.d.ts (2 hits each) and 0 times in the reachable set. package.json exports has only '.' and './styles.css'. No language-pack key changes.", "tests": "All at HEAD 1bbfc4b. PRE-FIX RED: new pin file on the unfixed tree: 3 failed / 3 passed. The card pin fails on the scope note; the trigger pin and the editor pin fail on Valid CEL shown beside the note. TARGETED after the fix: 14 files / 227 tests passed. PACKAGE: `pnpm exec vitest run packages/app-shell/` gave `Test Files 1078 passed | 1 skipped (1079)`, `Tests 10616 passed | 9 skipped (10625)`, lock VERDICT command-exit 0. TYPE: `pnpm --filter @object-ui/app-shell type-check` (echoed `tsc --noEmit && tsc -p tsconfig.test.json`) exit 0; --listFiles on tsconfig.test.json lists the new pin file, so the tests are type-checked. Dependency closure built first: `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build` exit 0 (29 of 47 projects). `pnpm --filter @object-ui/app-shell build` exit 0, `dist completeness: 1 package(s) complete (1006 emitted files verified)`. NEW PINS (CelPredicateField.scopeVerdict-11789.test.tsx, through the whole FlowNodeInspector): update-trigger card expression reads only Valid CEL (row mode and raw); control: bare `status == 'done' && previous.status != 'done'` unchanged; schedule Start with a declared variable: the note names `record`, no Valid CEL; one verdict: `trigger.status` (lint observed clean, then the scope note alone); editor contract with and without scopeIssue; control: the permission matrix RLS USING clause still reads perm.cel.valid. flow-scope.test.ts: the start-node pin reversed (it pinned the defect), plus a 9-row per-trigger table asserted at the Start node AND downstream. ABLATION (scripts/ablation-replace.mjs from the objectstack checkout, anchor-must-hit, restore proven; the tests import the subjects by relative source path, so no dist leg): [scope] in flow-scope.ts, the record push wrapped back in `if (!onStart)`. Anchor 1 to 0, blob 3180f147cab2 to 65c6870b91c0, then 8 failed / 51 passed: the card pin, the start-node pin and 6 record-trigger table rows. Restored: blob == HEAD 3180f147cab2, `git diff HEAD` empty. [verdict] in CelPredicateField.tsx, `issues.length === 0 && !scopeIssue;` changed to `issues.length === 0;`. Blob 51ef56396d7d to e2187dea09eb, then 2 failed / 57 passed: the trigger pin and the editor pin. Restored: blob == HEAD 51ef56396d7d, diff empty. Both went red, as predicted.", "gates": [ { "cmd": "pnpm exec vitest run packages/app-shell/", "exit": 0, "verdict": "Test Files 1078 passed | 1 skipped (1079); Tests 10616 passed | 9 skipped (10625)" }, { "cmd": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no diagnostics" }, { "cmd": "pnpm --filter @object-ui/app-shell build", "exit": 0, "verdict": "dist completeness: 1 package(s) complete (1006 emitted files verified)" }, { "cmd": "pnpm exec eslint (the 8 touched .ts/.tsx files)", "exit": 0, "verdict": "8 problems (0 errors, 8 warnings); every warning sits on a line outside the diff's hunks (pre-existing)" }, { "cmd": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK (scanned 7818 tracked text file(s); skipped 85 binary)" }, { "cmd": "pnpm check:test-path-roots", "exit": 0, "verdict": "check-test-path-roots: OK" }, { "cmd": "pnpm check:changeset-claims", "exit": 0, "verdict": "No pending changeset names a file this change touches." }, { "cmd": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "No test source names a pending changeset." }, { "cmd": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0" }, { "cmd": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "8 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11789-cel-scope-verdict.md" }, { "cmd": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a `major` bump." }, { "cmd": "i18n gates (check:i18n-keys / -drift / -dead-keys / -designer-parity)", "exit": null, "verdict": "NOT RUN, not applicable: no locale pack and no i18n.ts change" }, { "cmd": "pnpm check:readme-exports", "exit": null, "verdict": "NOT RUN, not applicable: package exports unchanged" } ], "files_changed": [ ".changeset/11789-cel-scope-verdict.md", "packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx", "packages/app-shell/src/views/metadata-admin/CelPredicateField.scopeVerdict-11789.test.tsx", "packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts", "packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.test.ts", "packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx (off surface)", "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.tsx (off surface)", "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.entryCondition.test.tsx (off surface, wording only)", "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.entryCondition.test.tsx (off surface, wording only)" ], "deviations": [ "No PR opened. The claim's file surface is breached, see files_surface_breach. pr_create and label-write are unspent.", "Zone 2 #3: the location is falsified, as stated in summary. The one-verdict rule is implemented where the two verdicts actually render.", "Zone 1 read against Zone 2 #2: `previous` stays out on a create trigger. See open_questions; not decided silently.", "origin/main moved to 1e1f09e (objectui#11810). It touches none of these files, so it was not merged, per the coordinator's instruction. The branch base stays 87f7b6c.", "The container restarted mid-run. The uncommitted work survived in the worktree and was committed as 1bbfc4b before anything else. The worktree /home/user/objectui-issue-11789 is kept, with dependency dists built, for the round that opens the PR." ], "mcp_calls": "0", "api_writes": "1: POST /repos/objectstack-ai/objectui/issues/11789/comments (this os-dev-report, via scripts/pm/post-stamped.mjs on the fleet relay). git push is not a REST write.", "open_questions": [ { "question": "Zone 1 says a record-triggered flow's scope includes `record` and `previous`. On a create trigger the engine binds `previous` only as null. Should Studio scope it there?", "options": [ "A: keep it out on create (implemented). `previous` and `previous.x` on a create trigger read the scope note. Cost: `previous == null` (constantly true there) is also flagged.", "B: put it in scope on create too, matching the literal Zone 1 wording. Cost: `previous.status` on a create trigger lints clean in Studio and the run fails, because a member read on null is a CEL evaluation error and evaluateCondition throws." ], "recommendation": "A. Actual business need: no shipped create-trigger condition reads `previous`; the corpus pinned in FlowNodeConfigField.entryCondition.test.tsx reads it only on update and write flows. Long-term soundness: the scope follows what the engine binds to a value. Preventing AI mistakes: B lets an AI write a predicate that faults at run time with a green editor. No scope expansion: A keeps Studio's existing behaviour for create." } ], "out_of_scope_findings": [ "carrier: no carrier (承接者:无) · noted, not filed. On a time_relative Start node, the engine's time-relative sweep hands each matched row to the run as `record`. Studio's flow-scope gives time_relative no trigger scope: it is not in RECORD_TRIGGER_TYPES, and its object is at config.timeRelative.object, not config.objectName. No wrong verdict today: with no declared variable the ref check has no roots and stays silent. The shipped producer (app-showcase showcase_task_due_reminder, which uses `{record.title}` templates) declares none. reach: none measured at a public door. Dedupe words: time_relative flow scope record Studio entry condition.", "carrier: no carrier (承接者:无) · noted, not filed. The record-change trigger accepts the pattern record-(before|after)-(create|insert|update|delete|write); Studio's RECORD_TRIGGER_TYPES lists 6 of the 10 tokens. Zero producers use the other 4 (before-create, before-insert, after-insert, before-delete): grep over objectstack origin/main 15ec50e5 examples and packages src. Control: the same grep for record-after-update hits 15 times in 3 example files. Studio's trigger select does not offer them." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim amendment —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T00:58Z. It amends claim6049225787after the dev's file-surface stop (report6049943145). The claim stands otherwise.-
File surface, added (measured by the dev):
packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.tsx: only the Start node entry condition's scope note (describeUnknownRefs), thescopeIssuehanded to theConditionBuilderit mounts, and theFLOW_TRIGGER_CONTEXT_SUBJECTSdoc comment, which this fix makes false;packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx: one optionalscopeIssueprop, forwarded toCelPredicateField;- wording-only edits in
FlowNodeConfigField.entryCondition.test.tsxandFlowNodeInspector.entryCondition.test.tsx, which namerecordas unbound on the Start node.
The claim's guess that the scope line came from
FlowExprIssue.tsxwas wrong. The entry condition's scope note isFlowNodeConfigField's own, so the one-verdict rule lands there.flow-ref-check.tsandi18n.tsstay untouched. -
Overlap declared: seat 2's objectui#11788 (claim
6049664797,area:workflow) may editFlowNodeConfigField.tsxfor the notify Recipients and field-mapping rows. This card's edit is the entry condition's scope note and itsConditionBuildermount, a different region. Whoever lands second mergesmain(never rebase). A note goes on objectui#11788. -
The dev's open question, answered A:
previousstays out of scope on a create trigger. The engine binds it there only asnull, and a member read onnullfails the run, so the scope note is the useful verdict. This follows the brief's own line ("scopepreviousexactly as the engine binds it") and triage6042943640's grounds. Update, before-update, write (the update leg) and delete scopeprevious. -
Clause-②: nostands: measured on the built declaration closure, none of the touched modules is reachable fromdist/index.d.ts.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11789, "status": "done", "addendum_to": "os-dev-report 6049943145 (blocked on the file surface). The claim amendment 6049965865 widened the surface and answered the open question A. This round opened the PR.", "branch": "claude/issue-11789-cel-scope-verdict", "pr": "https://github.com/objectstack-ai/objectui/pull/11849", "head": "b097f9a (1bbfc4b, the implementation, merged with origin/main 455c646; the merge was clean and main touched none of this PR's files)", "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent seat's id)", "premise_still_valid": true, "summary": "The draft PR objectui#11849 is open from b097f9a. Its first line is `Fixes #11789`, and `Clause-②: no` starts line 3. The body names: the falsified Zone 2 #3 location (the scope note is FlowNodeConfigField's own, not FlowExprIssue's); the per-trigger `record` / `previous` table, with create excluded because the engine binds `previous` there only as null and a member read on null fails the run (answer A); the side effect on edges leaving the Start node (the Problems panel is unchanged); the objectui#11788 overlap in FlowNodeConfigField.tsx; and the two out-of-scope notes as Acceptance notes. The implementation is unchanged from report 6049943145.", "tests": "On the merged head b097f9a. `pnpm --filter @object-ui/app-shell type-check` (echoed `tsc --noEmit && tsc -p tsconfig.test.json`) exit 0, after a rebuild of the dependency closure: `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build` exit 0, because main changed @object-ui/components. The targeted set (the new pin file plus the 13 sibling files): `Test Files 14 passed (14)`, `Tests 227 passed (227)`, lock VERDICT command-exit 0. The package-wide run (1078 passed, 1 skipped; 10616 tests passed) and the ablations were measured at 1bbfc4b, before the merge. They are not re-run on b097f9a, since main's app-shell commits (objectui#11783, objectui#11811) touch none of these files. The package-wide run on the merged head is CI's. CI at b097f9a when this was written: 42 check runs, 21 success, 3 skipped, 18 in_progress.", "gates": [ { "cmd": "pnpm --filter @object-ui/app-shell type-check", "head": "b097f9a", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no diagnostics" }, { "cmd": "pnpm exec vitest run (the 14 targeted files)", "head": "b097f9a", "exit": 0, "verdict": "Test Files 14 passed (14); Tests 227 passed (227)" }, { "cmd": "pnpm check:changeset-claims", "head": "b097f9a", "exit": 0, "verdict": "No pending changeset names a file this change touches." }, { "cmd": "pnpm check:pending-changeset-literals", "head": "b097f9a", "exit": 0, "verdict": "No test source names a pending changeset." }, { "cmd": "pnpm check:new-line-citations", "head": "b097f9a", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only, exit 0" }, { "cmd": "pnpm check:control-bytes", "head": "b097f9a", "exit": 0, "verdict": "check-control-bytes: OK (scanned 7830 tracked text file(s); skipped 85 binary)" }, { "cmd": "node scripts/check-changeset-presence.mjs", "head": "b097f9a", "exit": 0, "verdict": "8 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11789-cel-scope-verdict.md" }, { "cmd": "CI", "head": "b097f9a", "exit": null, "verdict": "in_progress (42 runs: 21 success, 3 skipped, 18 in_progress)" } ], "deviations": [ "None this round beyond those in 6049943145, which the amendment resolved.", "The PR's labels `tests` and `package: app-shell` were applied by labeler.yml, not by this run. Per the brief, no label write was made." ], "mcp_calls": "0", "api_writes": "4 across the run, all through the fleet relay (scripts/pm). (1) POST /repos/objectstack-ai/objectui/issues/11789/comments: report 6049943145. (2) POST /repos/objectstack-ai/objectui/pulls: pr_create (draft) gave #11849, body read back byte-identical, 9843 bytes. (3) POST /repos/objectstack-ai/objectui/issues/11849/assignees: label-write --assign os-support-ai, read back MATCHES. (4) POST /repos/objectstack-ai/objectui/issues/11789/comments: this addendum. git push (the merge b097f9a) is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "carrier: no carrier (承接者:无) · noted, not filed. On a time_relative Start node, Studio has no trigger scope while the engine binds `record`. Silent today: no wrong verdict at a public door. Recorded in PR #11849's Acceptance notes.", "carrier: no carrier (承接者:无) · noted, not filed. 4 of the 10 engine record-trigger tokens are absent from Studio's RECORD_TRIGGER_TYPES; there are zero producers (control: 15 hits). Recorded in PR #11849's Acceptance notes." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T01:35Z. PR objectui#11849, headb097f9a.-
PR shape: draft against
main, with455c646merged in (no rebase; the merge was clean). First lineFixes #11789, no other closing keyword.Clause-②: noat line start. Both commits carry only the model-free trailer pair. Assigned toos-support-ai. -
Scope: 9 files, +354/−56, on the claim's surface as amended (
6049965865), all underpackages/app-shell/src/views/metadata-admin/:inspectors/flow-scope.ts(the Start node'srecord, andPREVIOUS_TRIGGER_TYPES);CelPredicateField.tsxandinspectors/ConditionBuilder.tsx(one optionalscopeIssue, forwarded);inspectors/FlowNodeConfigField.tsx(the entry condition's region and theFLOW_TRIGGER_CONTEXT_SUBJECTScomment);- four test files (one new) and the changeset.
flow-ref-check.ts,FlowExprIssue.tsxandi18n.tsare untouched. Nothing is added to the package entry. No governed path. -
Diff read (the seat's own):
resolveFlowScopenow pushes therecordref at every node of a record-triggered flow, the Start node included. Only the per-field prefix still differs (bare on the Start node,record.downstream). The record-trigger gate (RECORD_TRIGGER_TYPESplus anobjectName) is unchanged, so a schedule, manual or API Start node gains nothing.record-after-deletejoinsPREVIOUS_TRIGGER_TYPES;record-after-createstays out.- In
FlowNodeConfigField, theexprIssue/unknownRefsblock moves above the control unchanged, so the control can handscopeIssue={unknownRefs.length > 0}to theConditionBuilderit mounts.CelPredicateFieldthen withholds "Valid CEL" (cleangains&& !scopeIssue). The lint, its findings andonLintChangeare untouched, and every other mount passes noscopeIssue.
-
Engine reading (the seat's own, objectstack
main15ec50e5):seedRunVariables(packages/services/service-automation/src/engine.ts) sets$record/recordand the record's flattened fields when the context carries a record, and setsprevioustocontext.previous ?? nullon every run.- The record-change trigger's
buildContextreadsctx.previous, which the data engine binds before the by-id update and by-id delete dispatch (bindPreImage). So a delete trigger'spreviousis the deleted row, and a create trigger's isnull. - The PR's per-trigger table matches both readings.
-
Ruling honoured: triage's amended direction (
6042943640):record/previousare in the record-triggered Start scope, and the scope verdict replaces the syntax verdict. The open question on create triggers was answered A in6049965865; theprevious-out-on-create row is pinned. -
Changeset sentences checked against the head:
- "
recordis in scope on the Start node … An edge leaving the Start node reads the same scope"; - "
previousfollows the pre-image … new here, delete … It stays out on a create trigger"; - "the scope note replaces "Valid CEL" … the permission set's row-level security clauses among them, are unchanged".
patchon@object-ui/app-shell. - "
-
Reverse verification (dev report):
- On
mainsource with the new pins: 3 red, 3 green (the controls). - Ablation through
ablation-replaceat1bbfc4b: puttingif (!onStart)back gives 8 red; dropping&& !scopeIssuegives 2 red. Both restored with the blob equal to HEAD. - The whole
@object-ui/app-shellsuite at1bbfc4bpassed 10616. The targeted 14 files atb097f9apassed 227.
- On
-
Gates: CI on
b097f9a(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean. -
Contract review: not required (
Clause-②: no; measured on the built declaration closure, none of the touched modules is reachable fromdist/index.d.ts).
Findings:
- A
time_relativeStart node gets no trigger scope in Studio while the engine bindsrecord. No wrong verdict reaches a user today: the shipped producer declares no variable, so the ref check has no roots →Acceptance notes - Four record-trigger tokens the record-change trigger accepts are not in Studio's
RECORD_TRIGGER_TYPES. Zero producers measured (the control grep forrecord-after-updatehits 15 times), and Studio's trigger select does not offer them →Acceptance notes
Overlap: seat 2's objectui#11788 may edit
FlowNodeConfigField.tsxin another region (declared in6049965865); it has no PR yet, so whoever lands second mergesmain.Landing: ready + auto-merge through the queue, now.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T01:54Z. PR objectui#11849 merged through the queue as5aa7f55at 2026-10-08T01:53Z.- The merge:
- One parent,
4a9fe31(objectui#11785's merge), an ancestor oforigin/main. - The landed tree equals
git merge-treeof that parent and the accepted headb097f9a(tree3e51f2d1on both). - 9 files, +354/−56.
- One parent,
- Content check,
4a9fe31→5aa7f55:- The Start-node gate
if (!onStart)on therecordpush inflow-scope.ts: 1 → 0. 'record-after-delete'inflow-scope.ts: 1 → 2 (it joinsPREVIOUS_TRIGGER_TYPES).scopeIssueinCelPredicateField.tsx: 0 → 3.- Control:
inspectors/flow-ref-check.tsis byte-identical on both sides, so schedule, manual and API flows gain norecordorprevious.
- The Start-node gate
- Closures: the PR's only closing keyword was
Fixes #11789. It is the only issue closed since the previous landing at 01:51Z.pm:dispatchedis removed. - Follow-ups: seat 2's objectui#11788 may edit
FlowNodeConfigField.tsxin another region; it mergesmainbefore landing. The two Acceptance notes (time_relativeStart scope; four unlisted record-trigger tokens) stay unfiled: no reach measured.
Generated by Claude Code
- The merge:
- added a commit that references this issue
on Oct 9, 2026
Filing gate ① — product defect with a named location and a reproduction. reach: flow Start node → Entry condition → Expression
record.status == 'done' && previous.status != 'done'.Who acts on it: objectui triage → the Studio / app-shell owner. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
What happens
The editor shows "Valid CEL" and, directly below, "
recordis not a reference in scope at this step." — two contradictory verdicts for one expression. (Barestatus == 'done' …shows only "Valid CEL".)The cross-surface scope question itself is objectstack-ai/objectstack#22096.
Expected
One verdict: the scope error, with the fix ("use
status— the record's fields are in scope directly here").Suggested direction (triage to rule)
Let the scope check downgrade the syntax verdict, and suggest the in-scope spelling.
Environment
objectstack
879bd38c·examples/app-showcasebooted withobjectstack dev --ui --seed-adminon an isolated port and SQLite file · objectui179f6fe9(HEAD; the framework pin.objectui-shaisa58626c8) served by the console's Vite dev server, perf numbers from avite buildof the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform adminadmin@objectos.aiunless stated.Duplicate check
rinrows.map(r, r.subject)) as not in scope #10538 (closed) — same flow-designer 'not a reference in scope' check, different false positiverecord-scope CEL lint refuses theapproot, which app-shell's predicate scope DOES bind —@objectstack/formula'sSCOPE_ROOTShas noapp#8155 (closed) — CEL scope-root disagreement familyDedupe words: CEL editor valid CEL record not in scope contradictory verdict
Filed by Claude Code (session
session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.Generated by Claude Code