Skip to content

fix(check): void action calls declare no variable; unknown call parameters not hidden (#953 items 1, 7) - #958

Merged
ako merged 10 commits into
mainfrom
fix/953-void-call-duplicates
Oct 3, 2026
Merged

ako merged 10 commits into
mainfrom
fix/953-void-call-duplicates

Conversation

@ako

@ako ako commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Part of #953: items 1 and 7. Group "e" handles items 2-6.

Item 1: output names on void action calls

Measured on mxbuild 11.13.0 (JTSBootLogboek copy, mxcli exec --no-check, then mxcli docker check):

Construct mxbuild
two void Java calls $ReturnValueName = … (microflow) 0 errors
two void JS calls $RefreshEntity = … (nanoflow) 0 errors
void call $V1 = …, then declare $V1 String (microflow and nanoflow) 0 errors
void call $V3 = …, then log … $V3 CE0109 Undefined variable 'V3'
void JS call $W = …, then a non-void JS call $W = … 0 errors
nanoflow: non-void call in each if/else branch CE0111
nanoflow: parameter + declare / declare in loop + after it / declare in each branch CE0111 each
microflow: declare in an error-handler body + declare after it CE0111

So the output name on a void call doesn't declare anything: it can't collide with a later variable, and you can't use it either.

What Studio Pro stores (FeedbackModule, Marketplace-authored, bson dump): void JS calls appear as OutputVariableName "Variable"/UseReturnVariable false, "Variable"/true and "ReturnValueName"/true. Exec writes the bare form call javascript action X(…) as ""/false. Dropping $X = from describe would therefore change Studio-authored models on a round trip. Describe keeps printing the stored name. Only the declaration count changes.

Changes:

  • voidCodeActions resolves a call's return type from the script (create java/javascript action … returns Void), or with -p from the project, which is opened lazily. If neither knows the action, the call still counts as a declaration. Guessing void there would hide a real CE0111.
    • Gotcha: the Java action reader returns a nil ReturnType for Void, while the JS reader returns VoidType. A mock that only had the typed form passed while the real project failed. Both forms are now covered.
  • MDL063 skips void calls, walks error-handler bodies (measured above), and now also runs for nanoflows. This fixes the false negative: the if/else duplicate in jt.mdl is now reported. The message names the document kind.
  • The check-time body validator (validateFlowBody) no longer reports duplicate names for microflows and nanoflows. It scoped them per branch and counted void calls, so it was wrong in both directions. MDL063 now owns that check. Rules keep the old check, since MDL063 doesn't run on them.
  • Describe's -- WARNING: duplicate output variable … model is invalid no longer fires for void calls.

Item 7: check --references and unknown parameter names

The Java/JS parameter-name check already existed. The repro (r1.mdl) hit it only because of two masking points:

  • cmd_check returned before the reference tier whenever a semantic rule reported an error. In the repro, MDL063 was a true positive, since ValidateEmail returns Boolean.
  • validateWithContext returned a flow's body errors instead of its reference errors.

Both lists are now reported. The catalog-backed tier still waits for a clean script.

There was also a real gap: call microflow / call nanoflow arguments were never checked against the callee's parameters. Measured CE1613 The selected parameter 'M.F.Bogus' no longer exists for both, including a callee with no parameters. They are now checked against script-created and stored flows. An action or flow known to take no parameters now reports any named argument; previously an empty list meant "unknown".

Note: CE1613 hides every other error in the same mx check run, so each fault was measured separately.

Test plan

  • New tests:
    • mdl/executor/validate_void_code_calls_test.go (void/non-void controls, nanoflow flow-wide, String contains not a producer, stored return types incl. nil-for-void, body validator handing duplicates to MDL063 with rule control, describe warning)
    • validate_flow_call_params_test.go (flow-call params with controls, body error not hiding reference errors)
    • cmd/mxcli/check_void_calls_test.go: end to end on a PedApp copy. Stored void JS action pair passes; Boolean control fails with MDL063; the r1 shape reports both MDL063 and has no parameter "email".
  • Revert checks: each fix removed, the test fails, fix restored. Void skip in MDL063 (unit + cmd test), nanoflow MDL063 call, nanoflow kind seeding, describe void skip, flow-call param check, body-error/ref-error combination, body-validator hand-off, cmd_check tier gate, Java nil-ReturnType handling.
  • Existing tests updated: bugfix_test.go duplicate tests and cmd_microflows_duplicate_output_test.go now assert MDL063 (the single owner). Removed two tests that asserted per-branch scoping for the body validator; mxbuild contradicts that scoping (CE0111 across branches).
  • go test ./mdl/executor/ ./mdl/linter/... ./cmd/mxcli/, make build, make lint, make check-conformance, make check-findings, make check-skill-mdl, make sync-skills
  • mxbuild evidence on a fresh copy of the JTS app, with NF_Ev_RefreshTwice (two void JS) and MF_Ev_VoidTwice (two void Java plus a declare of the same name):
    • mxcli check -p → Check passed! (it was MDL063 twice)
    • mxcli exec applied both flows
    • mx check → the 4 baseline errors only, 0 added
    • describe microflow MF_VoidTwice no longer prints the "model is invalid" warning
    • The repros: b1n.mdl passes, jt.mdl reports MDL063 (mxbuild CE0111), r1.mdl reports MDL063 plus the CE1613 parameter error, bp2.mdl reports the flow-call params

Follow-ups (not done here)

  • Using a void call's output name later is CE0109 in mxbuild; check does not flag it yet.
  • Describe's duplicate-output warning still treats exclusive if/else branches as separate scopes. mxbuild does not (CE0111). A test pins that behaviour, so it needs its own change.
  • The LSP calls ValidateMicroflow/ValidateNanoflow without a project resolver, so it still flags void calls to stored actions.

🤖 Generated with Claude Code

ako and others added 10 commits October 3, 2026 14:39
…race (#951)

SaveToFile now writes to a temp file in the cache's directory (VACUUM INTO,
manual-copy fallback into the same temp file) and renames it over the cache.
buildCatalog and refresh catalog communities no longer remove the cache first.
Opening a cache at the current schema version no longer writes to it: a write
on a file renamed underneath an open connection fails with
SQLITE_READONLY_DBMOVED. File-backed connections get a busy_timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Starlark rule reading a struct field this mxcli does not expose is now
reported at info level as "rule <ID> needs a newer mxcli (<detail>)"; other
rule failures stay errors. Every rule failure is marked RuleFailure, kept out
of BuildReport's score, summary and categories, and listed in its own
"Rules That Could Not Run" section (ruleFailures in JSON). A configured rule
severity no longer applies to the rule's own failure, and a rule file failing
to load on an undefined name hints at a newer mxcli.

Part of #952.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sync rules and CLAUDE.md (#952)

- init and every sync write .ai-context/mxcli-tooling.json; any -p command
  warns once on stderr when the binary is provably older (releases by number,
  nightlies by tag date, mixed by build date, dev builds never).
- init --sync-skills (alias --sync) refuses from an older binary instead of
  downgrading, and now also refreshes the bundled lint rules (by file name;
  user rules untouched) and the CLAUDE.md/AGENTS.md section between new
  mxcli:begin/end markers, keeping project notes outside them.
- The bootstrap script compares the PATH binary and ./mxcli with the stamp
  before using them, downloads MXCLI_TAG instead of linking an older binary,
  and downloads through a temp file so a symlinked ./mxcli is never written
  through. Binaries <= v0.24.0 cannot read the stamp; the script is their guard.
- init and new create mdlsource/ with a README.

Closes #952.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docker check ran mx update-widgets on the user's project with only the MPRv2
storage snapshotted, so an MPRv1 .mpr was rewritten permanently, and mx check
itself rewrote theme-cache/ and created deployment/sass/ on both formats, with
or without --no-update-widgets. Both mx steps now run on a temporary copy
(build output, caches and VCS folders skipped), mx output is rewritten to the
project's own paths, and the output says that widgets were normalised on a
copy and what that hides (#568, #646). docker build keeps its snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nto itself (#951)

Check copies the project's directory, so a missing .mpr (or a path in /tmp)
would copy an unrelated directory: fail early instead, and give the existing
fake-mx tests a project file of their own. With TMPDIR inside the project the
walk met its own copy and recursed until the path was too long; skip it.
Trim the custom-widgets skill back under the 700-line limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…MDL063 covers nanoflows

Two calls to a void action carrying the same output name (Studio Pro names a
JavaScript action's after the action, $RefreshEntity) are accepted by mxbuild
11.13.0, but check reported MDL063 (microflows) or 'already declared in this
scope' (nanoflows), and describe called the model invalid. Measured: a void
call's output name is inert - a later declare of the name builds clean, a use
is CE0109. The return type is resolved from the script or, with -p, the
project; an unresolvable action still counts.

describe keeps printing the stored `$X =`: Studio Pro stores the name with
UseReturnVariable=true, and the bare form would write an empty name.

MDL063 now runs for nanoflows too (flow-wide, as measured in mxbuild): a
duplicate output across if/else branches passed check and was CE0111. The
check-time body validator no longer reports duplicate names for flows - it
scoped them per branch and counted void calls; rules keep it.

Part of #953 (item 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… errors

check --references accepted a Java action argument naming no parameter
(CE1613 in mxbuild) whenever the script also had a semantic error: the
reference tier was skipped, and a flow's body errors were returned instead of
its reference errors. Both are now reported. call microflow / call nanoflow
arguments are checked against the callee's parameters too (CE1613, measured
on 11.13.0), and an action or flow known to take no parameters reports any
named argument.

Part of #953 (item 7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant