Skip to content

mcp: validate_expression at flow_condition judges a bare name without the flow's variables, so its type verdict and the door's shadowed-read warning can differ from the runtime flow door #33176

Closed

Description

@objectstack-fleet

domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx

Filing gate: ① a named-site behaviour gap, class a. It is carried out of #33147 (PR #33168): the seat's contract review 6868101966 lists it as outside that PR's diff, together with its finding F1. PR #33168 corrects the wording and says the tool cannot give these verdicts. ⛔ Not a claim; triage sets the lane and grade.
reach: public door, and a verdict that differs from the gate. MCP validate_expression (/api/v1/mcp tools/call, and stdio) at site: 'flow_condition'. Read at PR #33168 head c21d260c; not measured on a running deployment.

Business reason

An agent writes a flow whose variables include one named like a field, for example amount, and reads it bare in a condition. The runtime flow door warns about the shadowed read (warnShadowedFieldReads), and it types the bare name as the variable. validate_expression gets no variables. It types the bare name as the field, and it cannot give the warning. So the agent can be told ok, or be given a type verdict for the wrong binding, and then meet a different answer when it saves the flow.

What is true after PR #33168

  • flow_condition runs the runtime flow door's passes on a one-flow probe. The probe flow declares no variables, so warnShadowedFieldReads has nothing to compare against.
  • The tool's validator half (the shared expression validator) is never handed flow variables, so a bare name resolves against the object's fields only.

Expected

validate_expression at flow_condition takes the flow's declared variables, as an optional input or read from a named served flow. With them it binds bare names the way the door does, and it gives the door's shadowed-read warning. When no variables are given, it answers as today, and the description says so. The implementer proposes the input shape.

Dedupe: validate_expression flow variables shadowed field read warnShadowedFieldReads → #33147 (PR #33168, flow_condition parity, wording only for this point); none open.

Activity

  1. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p2 · pm:queue · 2026-11-14T00:49Z

    ⛔ Not a claim.

    • Row: at flow_condition, validate_expression types a bare name as the field where the runtime flow door types it as the same-named flow variable, and it cannot give the shadowed-read warning. That is a wrong verdict (p2).
    • Reach discount (−1 → p3): it needs a flow variable named like a field and read bare.
    • MCP focus raise (+1 → p2): the agent gets an answer that disagrees with the gate (rule 8).
    • Hung under the MCP epic [Epic] MCP: what an agent can read and write on the HTTP MCP door, and answers that agree with the gate #32897. Likely fix: let the tool take the flow's variables, an optional input, which widens the contract and must be declared.
  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  3. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#7 · sunhaolin · session_017xRBcSZ51q2s3nMfakhdJ3

    Claim: PM loop round 1
    Session: session_017xRBcSZ51q2s3nMfakhdJ3
    Account: sunhaolin
    Branch: claude/issue-33176-validate-expression-flow-variables
    Worktree: objectstack-issue-33176
    Domain: domain:server
    Seat: domain:server#7
    File surface: packages/mcp/src/mcp-http-tools.ts (the validate_expression tool: its input schema, handler and description, and gatePassFindings), packages/mcp/src/stdio-data-bridge.ts only where the stdio door must carry the same input, packages/lint/src/authored-site-expression.ts (the one-flow probe: declare the given variables so the door's warnShadowedFieldReads fires, and bind bare names as the door does), the validator half only through its existing options, their tests, root .changeset/. ⛔ Not packages/spec/**, not skills/**, not docs/adr/**. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (default judgment tier; dispatch-gates --tier: no path-derived mandate; Clause-② yes), effort: high
    Clause-②: yes (widening) — validate_expression gains an optional input carrying the flow's declared variables at site: 'flow_condition', so the tool accepts a call it refuses today (strictToolInput); with no variables the answer is unchanged. Precedent: #32993 and #32766 declared the same face in domain:server.
    Metadata-protocol: none
    Responsibility: packages/mcp validate_expression (objectstack) at flow_condition hands the gate probe and the shared validator no flow variables, so a bare name is typed as the field and the runtime flow door's shadowed-read warning never fires | none: PR #33168 only words the gap in the description | every MCP agent (HTTP /api/v1/mcp and stdio) authoring a flow whose variable shares a name with a field and reads it bare
    Thread-read: 6868800464
    Premise: holds (seat reading 2026-11-14T13:28Z on origin/main 295ce127: the tool's strictToolInput schema takes objectName, expression, site, severity, triggerKind, sweepObject, startCondition, field and no variables; authored-site-expression.ts header says the probe declares no variables so warnShadowedFieldReads (validate-expressions.ts ~L3055) never fires there)
    Serial constraints cleared: open draft PR #33205 (#33154, os-tesla) touches packages/lint/src/validate-expressions.ts and authored-site-expression.test.ts (object-extension validation rules, not the flow passes): file-level overlap, ordinary concurrency; the dev reads #33205's diff before pushing and keeps out of its hunks. No other open PR or live server claim holds packages/mcp/src/mcp-http-tools.ts or authored-site-expression.ts.

    Order: the dev proposes the input shape (the card leaves it open), states it in the PR body, and pins both arms: with variables, a bare name binds to the variable and the door's shadowed-read warning comes back; without, the verdict is byte-identical to today. The description says which.
    Epic: child of #32897 (M3), dispatched under the maintainer's 「mcp 专题的 p2 卡也可以派」 (this session, 2026-11-14).

    Dispatched 2026-11-14T14:11Z.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  5. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#7 · sunhaolin · session_017xRBcSZ51q2s3nMfakhdJ3
    os-dev-report

    {
      "issue": 33176,
      "status": "done",
      "branch": "claude/issue-33176-validate-expression-flow-variables",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/33255",
      "session": "session_017xRBcSZ51q2s3nMfakhdJ3",
      "premise_still_valid": true,
      "summary": "validate_expression gains an optional `variables` input (site flow_condition only, refused elsewhere like startCondition). The schema is z.array(FlowVariableSchema), reused from @objectstack/spec/automation, so a flow's own `variables` array is handed over as it stands. With it, authoredFlowProbe declares the variables, so the runtime flow door's own warnShadowedFieldReads fires, and the shared validator gets their names through its existing ExprSchemaHint.flowVariables, so a bare read of one is not typed as the field (left dyn, as at the gate). Without it the probe and the hint are unchanged, so every call answers byte for byte as before; the descriptions say the warning needs the variables. I chose the input over 'read a named served flow': that would need a new bridge member on both doors plus a flow-read fence, outside the file surface, and fails for a flow not yet saved. Measured: the validator's extra-bindings option exists (flowVariables, names only; no variable typing by `type` anywhere), and stdio serves the same tool definition via wireBridgeTools, so stdio-data-bridge.ts is untouched and a stdio/HTTP parity pin holds it. Head 4cae1f45ab.",
      "tests": "At head 4cae1f45ab. Full lint suite: Test Files 208 passed, Tests 8877 passed. Full mcp suite: Test Files 64 passed, Tests 1174 passed. Both ran under os-verify-lock, VERDICT command-exit 0. Lint authored-site-expression.test.ts went 50 -> 57: warnShadowedFieldReads moved from pass-silent-here to pass, with variables, at three slots (+2), plus a new #33176 block (+5): probe read-back, CONTROL, non-shadowing variable, bare-read-only, trigger kinds and start condition. mcp-validate-expression.test.ts went 72 -> 77: (a) the whole verdict equals the gate's on a flow declaring `title`, one shadowed warning and no type error; (b) CONTROL without the input gives type-mismatch-run-fails, equal to the definition-less bridge; the start condition; (d) a non-shadowing variable is clean; the spec's variable refusals; (c) three refusal rows. mcp-stdio-tools.test.ts went 18 -> 20 (stdio/HTTP parity). Ablations, each through ablation-replace.mjs and each restored with blob == HEAD and git diff HEAD empty: L1, the probe's variables spread deleted (the lint suite reads src): 6 failed | 51 passed. A1, the hint's flowVariables removed: 1 failed | 96 passed. A2, variables not handed to the gate: 3 failed | 94 passed. A3, first attempt void (a syntax error, no tests ran), redone as A3b, the off-site refusal disabled: 2 failed | 95 passed. MCP suites read lint through dist/, so the MCP leg of the probe is covered by A2, not by a dist rebuild.",
      "gates": "Local at 4cae1f45ab. typecheck for lint and mcp (tsc --noEmit && check:test-typecheck): VERDICT exit 0. pnpm lint: exit 0. dispatch-gates --commands derived 67 families: 65 exit 0, and 2 exit 3 PREREQUISITE NOT MET (check:dual-build-cjs-loads with no full build, check:lean-entry-closure with no objectql dist), NOT MEASURED and left to CI's Lint & Repo Gates. The --ran reconciliation, with exit codes recorded: 67 accounted, 65 run, 2 NOT-MEASURED (DERIVED from exit 3), 0 UNRUN. check:nul-bytes ran in the derived set, exit 0. CI check-runs on the head, read once after the PR opened: 17 in_progress, 1 success, 5 skipped, 0 failed (in_progress is the honest value; I did not wait). check-platform-checklist is not owed: no export was renamed or removed.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3 relay REST writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft), POST /repos/objectstack-ai/objectstack/issues/33255/assignees (pr_create leg, sunhaolin), POST /repos/objectstack-ai/objectstack/issues/33176/comments (this os-dev-report, post-stamped.mjs). Plus 3 git pushes (not REST).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: domain:server seat · served SKILL.md line (packages/mcp/src/skill-md.ts) and its Console mirror (ui/apps/console/src/pages/developer/objectstack-skill.ts) list validate_expression's inputs and do not name `variables`; both outside this claim's file surface; PR body carries `Skills-docs: follow-up owed — FILES`; nothing under skills/** describes the flow_condition inputs · noted, not filed",
        "carrier: none · observation, not a finding class: at a flow_condition the tool's validator half hands no engine-bound names (record, previous, vars, current_user) and, given `variables`, only the declared names, while the gate on a real flow also leaves node ids and screen field names untyped; a bare name held by one of those is still typed as the field here. Stated in the PR's Acceptance notes; no public-door repro measured · noted, not filed"
      ]
    }
  6. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#7 · sunhaolin · session_017xRBcSZ51q2s3nMfakhdJ3

    Seat note · 2026-11-14T15:03Z · ⛔ Not a claim

    The dev report (6879320769) is in. PR #33255 is open as a draft at 4cae1f45, with Fixes #33176 and the needs:contract-review label. Per the maintainer's 「契约复审等 review 座位」, the contract review belongs to the review seat. This seat does not run it.

    For the reviewer:

    • Face: Clause-②: yes (widening). validate_expression gains an optional variables input, allowed only at site: 'flow_condition' and refused elsewhere. Its type is z.array(FlowVariableSchema), reused from @objectstack/spec/automation, so a flow's own variables array goes in as it stands.
    • With the input: the one-flow probe declares the variables, so the runtime door's own warnShadowedFieldReads fires. The shared validator gets the names through its existing ExprSchemaHint.flowVariables, so a bare read of a variable is not typed as the same-named field.
    • Without the input: the answer is byte-identical to before. The CONTROL pin holds it.
    • Shape chosen: an input, not "read a named served flow". The latter needs a new bridge member on both doors and a flow-read fence, and it fails for an unsaved flow.
    • Stdio: stdio serves the same tool definition through wireBridgeTools, and a parity pin holds it.
    • Tests: four ablations, each red and each restored to HEAD.
    • Local gates: 65 of 67 green. check:dual-build-cjs-loads and check:lean-entry-closure were NOT MEASURED locally and are left to CI.

    Owed by the landing seat before ACCEPT: the PR body carries Skills-docs: follow-up owed — FILES. The files are the served SKILL.md line in packages/mcp/src/skill-md.ts and its Console mirror ui/apps/console/src/pages/developer/objectstack-skill.ts; neither names variables. Both are outside this claim's surface. The card is to be filed and the line rewritten to #N. Nothing under skills/** describes the flow_condition inputs.

    Noted, not filed: given variables, a bare name that is a node id or a screen field name is still typed as the field here. The gate leaves those untyped. This is stated in the PR's Acceptance notes; no repro on the public door.

  7. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#7 · sunhaolin · session_017xRBcSZ51q2s3nMfakhdJ3

    Landed · 2026-11-14T16:02Z

    PR #33255 merged at 9f736f2e, and Fixes #33176 closed this card. Read on origin/main: validate_expression imports FlowVariableSchema for its new optional variables input in mcp-http-tools.ts, and the one-flow probe in authored-site-expression.ts declares the variables it is handed.

    Delivered: at site: 'flow_condition', an agent can now hand over the flow's declared variables. A bare name that a variable shares with a field then binds to the variable, and the tool gives the runtime flow door's own shadowed-read warning. A call without variables answers exactly as before. The stdio and HTTP doors serve the same definition.

    Left, owned elsewhere:

    State change: pm:dispatched removed. This is an MCP epic #32897 M3 child, now closed.

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

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions