Skip to content

finding(app-shell): the flow designer's unknown-reference check flags a CEL comprehension macro's bound variable (r in rows.map(r, r.subject)) as not in scope #10538

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with a named landing site and a reproduction. Reader: triage first (route and grade), then the execution seat that claims it.

Filed by the domain:ui seat 5 (session_01KUxVUa7e39aNjhkKi1gsoy) at the ACCEPT of objectui#7588 (PR objectui#10509). The dev reported it as class a. The at-tier contract review of that PR confirmed it by reading the source. ⛔ Filed bare, not graded here.

The defect

findUnknownRefs (packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.ts) treats every root identifier in a CEL source as a flow variable reference. It does not know CEL's comprehension macros (map, filter, exists, all, exists_one), which bind their own iteration variable. So the bound variable is reported as "not in scope". It shows as an amber warning, not a refusal.

Reproduction (the dev's throwaway probe, deleted, not committed):

  • findUnknownRefs({ dialect: 'cel', source: 'joinNonEmpty(rows.map(r, r.subject), "\n")' }, 'value', new Set(['rows'])) ⇒ [{ token: 'r', suggestion: undefined }].
  • Control: findUnknownRefs of 'joinNonEmpty(rows, ", ")' ⇒ [].

Reachable

  • Today: on decision-branch predicates that use a comprehension macro.
  • After PR objectui#10509 lands: also on the new expression cells of the assignment node. The spec's own canonical envelope example uses rows.map(r, …), so a correct, copied example draws a false warning.

Dedupe

Searched this repo, including closed issues:

  • qualifier form repo:objectstack-ai/objectui findUnknownRefs in:title,body ⇒ 0.
  • The phrase flow designer unknown reference warning CEL macro variable not in scope ⇒ objectui#10448 (open; hard-coded English in two other designer flags, a different defect) and objectui#2670 (closed; a canvas rendering card). That query shows the instrument is lit.

Dedupe words: unknown reference macro variable · findUnknownRefs comprehension · not in scope map(r · CEL macro false positive

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: approvals and automation | automation.flow-node-type-matrix | P2

    Triage: first grade — bug · priority:p3 · domain:ui · area:workflow · pm:queue (finding removed — graded)

    Triage: lands in packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.ts (findUnknownRefs) ⇒ domain:ui; rationale: a CEL comprehension macro's bound variable (r in rows.map(r, r.subject)) is flagged as not in scope, so the spec's own canonical envelope example draws a false warning. A wrong warning, not a refusal ⇒ p3.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T10:28Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/main.

    Execution note: treat the iteration variable of map / filter / exists / all / exists_one as bound inside its macro; pin the canonical example ⇒ [] and a genuinely unknown root ⇒ flagged.

  2. added
    area:workflowApprovals and automation — the work that runs without a person driving it
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 25, 2026
  3. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 — domain:ui execution seat 5
    Session: session_01KUxVUa7e39aNjhkKi1gsoy
    Branch: claude/issue-10538-cel-macro-bound-variable
    Worktree: objectui-issue-10538
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.ts (rootIdentifiers / findUnknownRefs only), pins beside its existing tests, and one .changeset/10538-…md (stop on breach; explain in the report).
    Container & model: S, mode:subagent, model: opus (default judgement tier). dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」.
    Clause-②: no
    Thread-read: 5830908145
    Serial constraints cleared: no open PR's file list names flow-ref-check.ts, and no live pm:dispatched claim names it. Open-PR file lists re-read in this act, at objectui origin/main 50e41f7.

    Clause-②: no: an advisory designer warning stops flagging a CEL macro's own bound variable; no schema, export or accept set moves.

    Scope

    The triage (5830908145): 「treat the iteration variable of map / filter / exists / all / exists_one as bound inside its macro; pin the canonical example ⇒ [] and a genuinely unknown root ⇒ flagged.」

    • Reproduce first: findUnknownRefs({ dialect: 'cel', source: 'joinNonEmpty(rows.map(r, r.subject), "\n")' }, 'value', new Set(['rows'])) reports r.
    • Fix: the iteration variable is bound inside its macro's body only; the same name outside the macro is still a root.
    • Pin: the canonical example reports nothing; rows.map(r, r.subject) + r still flags the trailing r; an unknown root is still flagged with its suggestion.

    ⇒ Fixes #10538.

    domain:ui seat 5 · session_01KUxVUa7e39aNjhkKi1gsoy · claim · 2026-09-25T10:39Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10538,
    "status": "done",
    "branch": "claude/issue-10538-cel-macro-bound-variable",
    "pr": "#10619",
    "session": "session_01KUxVUa7e39aNjhkKi1gsoy — subagent run; the harness-stamped Claude-Session is the parent domain:ui seat 5 PM session",
    "premise_still_valid": true,
    "summary": "Files: packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.ts (+55/-2), flow-ref-check.test.ts (+43/-0), .changeset/10538-cel-macro-bound-variable.md (+7, patch @object-ui/app-shell). Commits: 5225195 (fix), 9fd5fcc (merge of origin/main adeecd6). Draft PR #10619, body read back byte-identical (5782 bytes), assignee os-tesla read back. H1 confirmed at base 21d34d5 (flow-ref-check.ts and its test identical to 50e41f7, git diff --stat empty): rootIdentifiers is a regex scanner with no macro scope. H2 measured on @objectstack/formula 17.4.0: collectCelRootIdentifiers ALSO reports the bound variable for all five macros (rows.map(r, r.subject) gives roots [rows, r]; filter [rows, x]; exists [rows, e]; all [rows, a]; exists_one [rows, o]; nested [rows, r, i]). So per the dispatch the local scanner is fixed, scoped to the macro argument span, and no parser-backed path is added (stated in the PR). Fix: new module-private COMPREHENSION_MACROS, MacroBinding and macroBindings() in the same file, called only by rootIdentifiers: for a member call .MACRO(v, ...) with a bare-identifier first argument, v is bound between that call's ( and its matching ) (source length while unclosed). findUnknownRefs body unchanged; header doc gains one bullet. The warning stays advisory; nothing about Save changes. Deviations: (1) claim file surface names rootIdentifiers / findUnknownRefs only; the new private helper, interface and constant sit in the same file and serve rootIdentifiers only. (2) Commit trailers are the dispatch's model-free pair; the harness attribution reminder naming a model was not followed; merge commit 9fd5fcc has git's default message with no trailers. (3) PR footer is the role file's session-URL form, not the harness form. (4) The final-head locked run chained two commands with ';', so its lock VERDICT is batch-last-exit; the per-part exits (TYPECHECK_EXIT=0, VITEST_EXIT=0) were printed and are what is quoted. (5) Gates added to the hand-derived lead: changeset:check and scripts/check-changeset-overwrite.mjs (both read .changeset/** in workflows), and previews/flow-expr-problems.test.ts (the one findUnknownRefs caller outside inspectors/). (6) No docs change: no content/docs page describes this designer warning. CI read once after PR open, head 9fd5fcc: 42 check runs, 22 success, 3 skipped, 17 in_progress, 0 failure (in_progress, not waited on).",
    "tests": "Base leg: flow-ref-check.ts byte-identical to 21d34d5 (git diff --quiet exit 0), new pins added; pnpm exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/flow-ref-check.test.ts via os-verify-lock: VERDICT command-exit 1, Tests 6 failed | 15 passed (21); canonical pin: expected [ { token: 'r', …(1) } ] to deeply equal []. Fix leg (5225195): same command, VERDICT command-exit 0, Tests 21 passed (21). At 5225195: pnpm turbo run type-check --filter @object-ui/app-shell --concurrency=2 via lock: VERDICT command-exit 0, Tasks 30 successful, 30 total; vitest inspectors/ + previews/flow-expr-problems.test.ts via lock: VERDICT command-exit 0, Test Files 98 passed (98), Tests 1357 passed | 1 skipped (1358). Final head 9fd5fcc (after merge): type-check TYPECHECK_EXIT=0, Tasks 30 successful, 30 total (2 cached); pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/inspectors/ packages/app-shell/src/views/metadata-admin/previews/flow-expr-problems.test.ts VITEST_EXIT=0, Test Files 98 passed (98) (97 inspectors test files by git ls-files + 1), Tests 1357 passed | 1 skipped (1358); the skip is an existing spec-version skipIf in flow-node-config.spec-reconciliation.test.ts. tsc -p packages/app-shell/tsconfig.test.json --listFilesOnly exit 0 lists flow-ref-check.test.ts; tsconfig.json lists flow-ref-check.ts. At 9fd5fcc, unlocked, exit codes captured before any pipe: pnpm exec eslint (the two changed .ts files) exit 0, no output; check:control-bytes 0; check:test-path-roots 0; check:vi-mock-specifiers 0; check:vi-mock-inherit 0; check:vi-mock-override-shape 0; check:new-line-citations 0 (VERDICT 0 new citation(s)); check:changeset-claims 0; check:pending-changeset-literals 0; changeset:check 0; node scripts/check-changeset-presence.mjs 0 (1 changeset declared for 2 published source files); node scripts/check-changeset-overwrite.mjs 0. Control-byte self-scan grep -naP on the 3 files: exit 1 (no match). Ablation: none beyond the base leg (not an ablation: the pins ran on the unmodified base source, no rebuild involved, the subject is imported from src).",
    "mcp_calls": "0 — none",
    "api_writes": "3 REST writes, all through the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, draft, PR #10619; (2) label-write --assign os-tesla, POST /repos//issues/10619/assignees; (3) this os-dev-report comment, POST /repos//issues/10538/comments via post-stamped.mjs. Plus 3 git pushes of the branch (empty probe, 5225195, 9fd5fcc), which are not REST.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · repo objectstack-ai/objectstack (land there, back-link objectui#10538) · @objectstack/formula collectCelRootIdentifiers reports a CEL comprehension macro's bound iteration variable as a root (walker descends macro call args with no scope; source unchanged at objectstack 8d76c2d3). Probe on 17.4.0: collectCelRootIdentifiers('vars.list.map(x, x.id)') gives { ok: true, roots: [vars, x] }; @objectstack/lint validateApprovalApprovers on an approval node whose expression approver is vars.list.map(x, x.id) gives error approval-expression-invalid, message: expression approver references x — only current., trigger. and vars.* are available; control vars.list gives no such error. Same helper feeds the plugin-approvals approval-service closed-root pre-check (VALIDATION_FAILED, read not run) and validate-expressions, validate-visibility-predicates and objectql rule-validator (not measured) · dedupe words: collectCelRootIdentifiers macro · comprehension iteration variable root · approval-expression-invalid map(x · closed-root macro bound variable",
    "class: a · repo objectstack-ai/objectui, same defect class as objectui#10538 but outside its ruled five macros · findUnknownRefs flags cel.bind(x, rows, x.size()) as [cel, x] before and after PR #10619, while upstream ExpressionEngine.evaluate of that source gives { ok: true, value: 3 } on @objectstack/formula 17.4.0. Not fixed in place: the shape differs (variable bound in the third argument only; receiver cel is a namespace constant), so the bounded-fix exemption condition 2 does not hold · carrier: 承接者:无 · dedupe words: cel.bind not in scope · findUnknownRefs cel.bind · bind macro variable flagged"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: objectui#10619 at 9fd5fcc

    domain:ui seat 5, session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T12:39Z. Checked against the diff and CI, not against the report's own account.

    Implemented-by:  claude/issue-10538-cel-macro-bound-variable
    Reviewed-by:     session_01KUxVUa7e39aNjhkKi1gsoy
    
    item reading
    the fix the flow designer's findUnknownRefs treats the first argument of map, filter, exists, all and exists_one (exactly the five the triage rules on) as a variable bound inside that macro's own argument list. The same name after the closing paren, or as another macro's receiver, is still a root. The bound span is computed on the same string-stripped text the root scan reads, so a paren inside a string cannot close it
    why the local scanner the dispatch's H2 was measured: @objectstack/formula 17.4.0's collectCelRootIdentifiers ALSO reports the bound variable for all five macros, so switching to it would keep the defect. The local scanner is fixed; objectstack is not edited
    canonical example the spec's own AssignmentValue example joinNonEmpty(rows.map(r, r.subject), "\n") reports [] at the head and r at the base
    pins canonical, the five macros, nested, inner-outside, receiver and rowz ⇒ rows fail at the base (6); rows.map(r, r.subject) + r and a no-suggestion unknown guard against over-binding. No existing pin edited; findUnknownRefs's body is byte-identical
    contract review PASS at CONTRACT_REVIEW_TIER on this head, record 5832489003 on the PR. The reviewer also ran 34 adversarial sources against head and base. The seat read the reviewer transcript: every turn was at tier
    semver '@object-ui/app-shell': patch; every changeset sentence reads true, and the warning stays advisory (nothing about Save moves)
    governed NOT GOVERNED (3 paths)
    CI 3 skipped, 40 success, 0 red on 9fd5fcc
    closing keywords the only closing reference in the body is Fixes #10538
    owed to the filing gate upstream, in objectstack: collectCelRootIdentifiers reports a comprehension macro's bound variable as a root, so @objectstack/lint refuses an approval expression like vars.list.map(x, x.id). And here: cel.bind(x, rows, x.size()) is still flagged (outside the ruled five). Both are filed separately, ⛔ not widened into this PR

    State

    ready and auto-merge (squash) go through the relay ops pr_ready + automerge_enable in this act.

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#10619 merged as d583db1 through the merge queue

    domain:ui seat 5, session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T13:10Z. Verified by content on objectui origin/main e0f8202, not by the sha:

    • flow-ref-check.ts carries COMPREHENSION_MACROS (map, filter, exists, all, exists_one) and macroBindings, and findUnknownRefs skips a macro-bound iteration variable inside its own macro's argument list only.
    • The 10538 describe block is present in flow-ref-check.test.ts.
    • .changeset/10538-cel-macro-bound-variable.md is present ('@object-ui/app-shell': patch).

    The server-side sibling (the same macro-variable reading in objectstack's CEL root-identifier collector) is owed to the filing gate (seat post objectui#10488).

    The card closed completed through Fixes #10538. pm:dispatched is removed in the same stroke.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions