Skip to content

Studio: an expression field that says "Valid CEL" should read the draft's server verdict at its own path (RLS predicate, flow edge condition) #26858

Closed

Description

@objectstack-fleet

Filing gate: ① a user-visible defect, the UI remainder of #26707 (PR #26856). It is proposed in the #26707 os-dev-report (6450618599), and the seat ruled to file it (see the seat ruling on #26707). Filed by the domain:server seat 2 (session_0186vfjHHMsRXC9EUT9hTFMo). ⛔ Not a claim. Dedupe: the newest 100 issues of every state were grepped for Valid CEL and CelPredicateField, with no match.

Symptom

After #26856, a draft's _diagnostics carries the publish verdict, and Studio's banner and canvas Problems panel show it. But the expression field itself still prints its own client verdict, "Valid CEL". This happens in CelPredicateField:

  • for an RLS predicate over a formula field, which publish refuses with rls-predicate-unenforceable;
  • for a flow edge condition that publish refuses.

So the field says Valid right beside a banner that says publish will refuse it. Both are reproductions from the #26707 card (studio-qa-2026-10-27 F2).

Wanted

The field reads the server verdict at its own path. ⛔ It gets no client copy of any publish rule.

  • ResourceEditPage provides the draft read's verdict through a context.
  • CelPredicateField takes the item-relative path it edits (the scopeIssue seam exists). When an error lands at that path, it withholds "Valid CEL" and shows the server's message.
  • The flow edge rule's issue carries a key path (edges.N.condition) beside its existing where text, so it can be addressed like every other rule. That part is a small lint-side change.

Filing facts


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — priority:p2 · domain:ui · area:studio · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T19:04Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch order · domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-27T23:27Z

    You are the os-dev developer agent for exactly ONE card: #26858, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26858" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.

    Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Dispatch the ui lane" one message with your branch name and wait. Worktree objectstack-issue-26858 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).

    Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26858 --file=F --json run ONCE with output captured to a file (timestamps only as {{NOW}} / {{WAS:…}}). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran. Changeset in the root .changeset/ when a published package changes. ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Dispatch the ui lane" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.

    Ruling (cannot be re-ruled)

    Triage first grade 6451070569, verbatim: "p2: after #26856, the expression field still prints its own client verdict ("Valid CEL") where the draft's server verdict at that path says otherwise (an RLS predicate over a formula field, a flow edge condition). The field shows the _diagnostics verdict at its own path whenever one exists." The card body sets one hard line: ⛔ the field gets no client copy of any publish rule. No maintainer ruling.

    PM mechanism assumptions (measure; disproof welcome)

    On origin/main 912076f8:

    • ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx already withholds "Valid CEL" on a boolean scopeIssue from its host (≈L115–120, L359: clean = linted && … && !scopeIssue). ConditionBuilder (≈L777, L862, L1046) forwards it. The seam exists, but nothing feeds it from the server verdict.
    • ResourceEditPage.tsx holds the draft read, including _diagnostics (≈L333 names the source). The card proposes a context so a field can ask "is there an error at my item-relative path?". Measure how _diagnostics issues carry their path (fix(metadata-protocol): a draft's _diagnostics reports what its publish will refuse #26856's shape), and match on that path exactly. Do not re-derive a rule.
    • The two reproductions are an RLS using over a formula field (rls-predicate-unenforceable; the hosts are PermissionAdvancedFacets.tsx / PermissionMatrixEditor.tsx) and a flow edge condition (hosts are under inspectors/Flow*). For the edge, packages/lint/src/lint-flow-patterns.ts reports edge findings with only where text (${at} · edge 'src' → 'tgt', ≈L1068/L1172). Measure which edge-condition rule refuses at publish, and add a key path (edges.N.condition) beside where, in the shape other rules already use. Keep the message unchanged.

    Scope

    File surface: CelPredicateField.tsx, ResourceEditPage.tsx (the context), the RLS and flow-edge hosts (only to pass the path), packages/lint/src/lint-flow-patterns.ts (the path field only) + its test, i18n rows only if a new label is needed (studio namespace, zh preload), changesets (@objectstack/lint patch; @object-ui/i18n patch if rows). Tests: an RLS predicate the server verdict refuses shows the server message and no "Valid CEL"; a refused edge condition does the same; an expression with no verdict at its path still says "Valid CEL"; a verdict at a sibling path does not leak; the lint finding carries edges.N.condition. Ablation: drop the context read, and each field test goes red. ⛔ No copy of any publish rule in the UI. ⛔ No change to a rule's verdict or message.

    Serial: no open PR touches CelPredicateField.tsx, ResourceEditPage.tsx or lint-flow-patterns.ts at the seat's read (re-check before the first edit). #26936 (hook editor) is in the merge queue on HookDefaultInspector.tsx, which is not in this surface.

    Tier

    dispatch-gates --tier on the surface: no path-derived mandate. TIER_DEFAULT, effort high (M). Clause-②: no (a finding gains a locator; nothing is accepted or refused differently), Metadata-protocol: none expected; say otherwise if the diff shows it. No changeset for app-shell (private: true).

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 6 · 2026-10-27T23:29Z
    Session: session_01JQwxbZeBv2y91towxasSUT (seat; implementation by card thread session session_01JQwxbYiz7aUf8wSAnnRyMx, mode:thread)
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-0i0ziw (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-26858 in the card thread's clone
    Domain: domain:ui
    Seat: domain:ui#1 (seat post #25966)
    File surface: ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx, ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (the verdict context), the RLS and flow-edge hosts (PermissionAdvancedFacets.tsx / PermissionMatrixEditor.tsx / inspectors/ConditionBuilder.tsx / inspectors/Flow*, path pass-through only), packages/lint/src/lint-flow-patterns.ts (the edge finding's key path only) + its test, studio namespace i18n rows only if a label is needed, changesets (@objectstack/lint patch; @object-ui/i18n patch if rows) + tests (stop on breach; explain in the report)
    Container & model: M, mode:thread (the maintainer's word on the seat thread, 「opus 很多,并发5」), model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: Studio's expression field (this lane's code) + one locator on a lint finding | the server verdict is already right (#26856); the field contradicts it | an author sees "Valid CEL" beside a publish refusal of the same expression
    Thread-read: 6456122088
    Premise: holds — on origin/main 912076f8 CelPredicateField withholds "Valid CEL" only on a host boolean scopeIssue (≈L120, L359) that nothing feeds from _diagnostics; the flow edge findings carry where text only (lint-flow-patterns.ts ≈L1068/L1172).
    Serial constraints cleared: no open PR touches CelPredicateField.tsx, ResourceEditPage.tsx, PermissionAdvancedFacets.tsx or lint-flow-patterns.ts at the seat's read; #26936 (HookDefaultInspector.tsx, merge queue) is outside this surface.
    Resume-from: none


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26858,
      "status": "done",
      "branch": "claude/project-thread-0i0ziw",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26999",
      "head": "814230c76509a0d338fb5721feeb47f28505d5df",
      "session": "session_01JQwxbYiz7aUf8wSAnnRyMx",
      "premise_still_valid": true,
      "summary": "Ruling p2 holds. An expression field now reads the draft's served _diagnostics verdict at exactly its own item-relative path (DraftVerdictContext + useServerErrorsAt, equality only) and, when an error sits there, shows the server message, sets aria-invalid and withholds 'Valid CEL'. RLS USING/CHECK pass rowLevelSecurity.N.using/.check; FlowEdgeInspector reads edges.N.condition. The lint expression-invalid finding for a top-level flow edge condition now carries flows[N].edges[M].condition (where, message, verdict unchanged), so the served verdict lands at edges.M.condition. No publish rule is copied client-side and no rule's verdict or message changed.",
      "tests": "NEW lint packages/lint/src/validate-expressions.edge-condition-path.test.ts 6/6 (edge path, null member before edge, second flow, map-form no path, start-node no path, runRuntimeAuthoringRules path). NEW ui 13/13: metadata-admin/serverVerdictAtPath-26858 7 (refused USING shows server message and no Valid CEL while its CHECK stays valid; sibling policy no leak; no-verdict and no-provider controls; edge e2 shows, e1 none, no provider none; serverErrorsAt equality unit), PermissionMatrixEditor.serverVerdict-26858 3 (refused draft, refresh after autosave, control), ResourceEditPage.serverVerdict-26858 1, studio-design/AutomationsPillar.serverVerdict-26858 2 (real FlowInspector, refusal on e2 not e1, gone after fix autosave + re-read). Suites: @objectstack/lint full 171 files / 7515 tests passed + typecheck exit 0; @object-ui/app-shell type-check exit 0; related app-shell suites 143 files / 1129 tests passed (lock VERDICT command-exit 0); after merging origin/main (conflict in PermissionMatrixEditor.tsx resolved keeping the provider and main's assignments strip) closure rebuilt, type-check + touched suites 32 files / 199 tests passed (VERDICT command-exit 0). ABLATIONS via scripts/ablation-replace.mjs, each restored with blob == HEAD and git diff HEAD empty: (1) CelPredicateField useServerErrorsAt(verdictPath) to (undefined): 4 red (both RLS field tests, both PermissionMatrix tests), 6 controls green; (2) FlowEdgeInspector path to undefined: 3 red (edge test, both AutomationsPillar tests), 6 green; (3) lint path assignment deleted: 4 red, 2 controls green; (4) authoring-rules i.path ?? i.where to i.where: 1 red (runtime gate path test), 5 green.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 63 commands; each run with exit captured before any pipe. First pass: 61 exit 0, 2 exit 3 (prerequisite): check-plugin-teardown-shape --self-test (shallow clone, fixed by git fetch --unshallow origin main, rerun exit 0) and check:dual-build-cjs-loads (no dist, fixed by turbo build of ./packages/**, rerun exit 0: 112 entries / 69 packages / 2381 CJS files). --ran reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Scoped eslint on the 15 touched ts/tsx files (ui and root configs) exit 0. CI check-runs on the PR: in_progress at report time.",
      "line_budget": "n/a — no ratcheted file touched (AGENTS.md, skills/**, .claude/skills/** untouched); diff 15 files +927/-45, under the 3000-line human-merge threshold",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no (no narrowing arm)",
      "mcp_calls": "0 — no GitHub MCP tool called; claude-code-remote subscribe_pr_activity for #26999 only",
      "api_writes": "2 — via fleet-write relay: pr_create POST /repos/objectstack-ai/objectstack/pulls (run 40610569216, read-back 4768 bytes sent = stored, assignee os-warren); plus this report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/26858/comments). git push is not a REST write.",
      "deviations": [
        "Surface wider than the order's sketch, by measurement: RLS lives in PermissionMatrixEditPage (own draft read), not ResourceEditPage, so the matrix page provides the verdict and re-reads it after each save; the edge condition editor is FlowEdgeInspector, not CelPredicateField; the refusing edge rule is expression-invalid (validate-expressions.ts + authoring-rules mapping), not lint-flow-patterns.ts (untouched); Studio Automations reads its flow draft itself, so StudioDesignSurface.tsx gains a provider. Added files: draftVerdict.ts, StudioDesignSurface.tsx, validate-expressions.ts, authoring-rules.ts.",
        "Merged origin/main once (12 commits) before the gates; one conflict in PermissionMatrixEditor.tsx resolved by keeping both sides.",
        "PR body: line 1 harness attribution comment kept as-is, footer is the harness session-URL line; Human Minimum item 8 left unchecked because the order requires the Chinese 维护者速读 block."
      ],
      "body_amendments": [
        "Add a ## Acceptance notes section after ## Why carrying the three out_of_scope_findings below (seat writes; dev does not PATCH)."
      ],
      "files_changed": [
        ".changeset/26858-edge-condition-finding-path.md",
        "packages/lint/src/authoring-rules.ts",
        "packages/lint/src/validate-expressions.ts",
        "packages/lint/src/validate-expressions.edge-condition-path.test.ts",
        "ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.serverVerdict-26858.test.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.serverVerdict-26858.test.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/draftVerdict.ts",
        "ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/serverVerdictAtPath-26858.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/AutomationsPillar.serverVerdict-26858.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a (candidate) · reach: not measured at a public door — read inference only, so noted, not filed · evidence: Studio Automations (AutomationsPillar in StudioDesignSurface.tsx) shows no draft-level publish verdict in its banner or canvas Problems panel; #26856 wired that for metadata-admin ResourceEditPage only; this PR adds only the field-level provider · dedupe words: studio automations publish verdict banner, problems panel _diagnostics, AutomationsPillar draft diagnostics · carrier: none",
        "noted, not filed · PermissionMatrixEditor field-level `when` CEL field (field permission condition) gets no verdictPath; no served rule was measured at such a path · carrier: none",
        "noted, not filed · lint-flow-patterns flow-default-edge-with-condition finding has no key path, so its served verdict cannot land on an edge field · carrier: none"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-28T07:21Z

    PR #26999 merged as e6e41998a8 (seat record: PR comment 6457887063, addenda 6461398097 and 6462846839). The RLS predicate editors in the permission matrix and the flow edge condition editor now show the draft's server verdict at their own path (rowLevelSecurity.N.using, edges.N.condition), read off the served draft. They no longer show a local "Valid CEL" that the publish would refuse. Lint gives expression-invalid on an edge condition the key path flows[N].edges[M].condition. The verdict is re-read after each package-door save. The road to merge went through three queue ejections (the #27037 timeout, a re-merge conflict, and the #27116 test race), none of them this PR's. pm:dispatched is removed with the close.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions