Skip to content

check/fmt: read several files as one script set; report and pair stub-then-real flows (#905 part 2) - #908

Merged
ako merged 6 commits into
mainfrom
fix/905-stub-then-real-detection
Oct 1, 2026
Merged

ako merged 6 commits into
mainfrom
fix/905-stub-then-real-detection

Conversation

@ako

@ako ako commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Part of #905

Part 2 of #905: find stub-then-real script sets. The splice fix for the underlying refusal (G2) is the parallel PR on fix/905-splice-error-handler-return. This PR does not touch mfmutator.

What changes

  • check and fmt now take several files and read them as one script set, run in the order given. Before this, both took exactly one file, and so did exec, so no command ever saw the set. One file alone behaves exactly as before.
  • New cmd/mxcli/script_set.go: findFlowRedeclarations finds a microflow or nanoflow that two create or modify statements declare, in different files of the set or twice in one file. Each site is reported as file:line with its language version.
  • check (with or without -p) prints an MDL-STUB01 warning before the per-file checks. It names both statements, says when they are under different headers, and advises dropping the stub, since a self-recursive flow is created in one statement after A recursive microflow cannot be created in one statement (exec: microflow not found; check passes) #843. It is a warning, so the exit code is unchanged. A structured --format is refused for several files because it writes one document per script.
  • fmt --upgrade [-p] file… reports each pair, then decides the header for the files that share a redeclared flow (grouped transitively) together. If any file in the group cannot take the header, the others are declined it too and fmt says why. A file cannot take the header when its upgrade is blocked, or when, with -p, exec would refuse a statement (MDL-V1-REBUILD) and --force-header is not given. The per-file verdict is the same code (headerRefusals). --force-header puts the header on both.
  • Registered as a rule: documented in docs-site/src/appendixes/error-messages.md, with a CHANGELOG entry and a finding in cmd-mxcli.jsonl.
  • First commit is a pure refactor: check's body becomes runCheckFile, which returns its exit code where it called os.Exit.

Design choices

  • "Neither" is the safe side of a split decision. Both files then stay mdl 0, and each run rebuilds stub and real in order, so the real flow ends up stored. This is what happened before any upgrade.
  • A group of one file (a flow declared twice in one file) needs no header coordination, because a file has one header. It is still reported in a multi-file run. A single-file run reports nothing, to keep single-file behaviour unchanged as the item asked.
  • exec still takes one file. Running a set through one exec is a follow-up.

Test plan

  • TestFmtUpgrade_ScriptSetDecidesStubThenRealHeaderTogether (PedApp copy; the real flow is stored by mxcli). The stub's file is refused under mdl 1 (it changes a loop body). Over the set, neither file takes the header and MDL-STUB01 is reported with both file:line. Controls: the real file alone takes the header (single file unchanged), and it also takes it in a set with no redeclaration. --force-header puts it on both.
  • TestCheck_ScriptSetWarnsOnStubThenReal: warns with both sites, exit 0. Controls: a set with no redeclaration, and the same pair in one file checked alone, give no warning.
  • TestFindFlowRedeclarations: cross-file and in-file pairs, quoted names and create or replace nanoflow, plain create ignored, line numbers, mixed versions, file grouping.
  • Revert checks: with the decline map ignored, the fmt test fails with "the pair was split: the real file took the header its stub cannot take". With the set scan removed from check, the check test fails with MDL-STUB01 missing.
  • Ran rehearsal repro repros3/stub-then-real-loses-flow by hand with this build: fmt --upgrade -p -w over both files declines the header on both. Exec of the stub and then the real file, run twice, leaves the real flow stored (call … on error / log … 'done'), where the original repro left the placeholder.
  • Ran: make build, go test ./cmd/mxcli/... (pass), make lint, make check-conformance, make check-findings. No write path changed, so neither the twice-exec rule nor the GUID rules apply, and I did not run the integration suites.

🤖 Generated with Claude Code

ako and others added 6 commits October 1, 2026 17:25
…exiting

The check command's body becomes runCheckFile, which returns the exit code
where it called os.Exit. Behaviour is unchanged for the one file check takes;
it lets a caller check several files and combine their codes (#905).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r stub-then-real flows (#905)

check and fmt --upgrade take several files, run in the order given. A flow
two create-or-modify statements of the set declare - a stub, then the real
flow - is reported as MDL-STUB01 (a warning) naming both statements, and
fmt --upgrade decides the language header for the files sharing such a
flow together: all take it or none does. Decided file by file, the stub's
file was declined the header while the real flow's file took it, and run in
order the mdl 0 stub rebuilt the real flow every run while the mdl 1 real
statement was refused, with mx check clean. One file alone is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an already-headed file is reported, not declined (#905)

canTakeHeader read any written header as 'already under it', so a stub
pinned `mdl 0;` let its real file take `mdl 1;` and the pair split again.
A file already carrying the header cannot be declined it (fmt never removes
one); fmt now says the pair stays split instead of 'no language header added'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed '{1}' template (#905)

A log, show message or validation feedback whose message is an expression
(`'failed for ' + $User/Name`) is stored as the template '{1}' with the
expression as its parameter, and describe prints that form. declaredMatches
compared the two spellings as different statements, so wherever the built
comparison does not decide (a spliced or Studio Pro-drawn flow) the
statement diff replaced the activity on every run: absorbed by the write
elision on the main path, refused under mdl 1 when the message sits in a
stored activity's error handler ("cannot replace … it has an error
handler"). That was the second run of CapTrack's ACT_Export_Excel once the
splice could grow it.

matchValue normalises the three statements to the builder's stored form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eturn when spliced (#905)

Under mdl 1, `create or modify` refused to grow a stored flow by an activity
whose custom error handler ends in `return` ("an error handler in the
fragment ends at an end event of its own … a return inside an error handler
is not spliced yet"), as an insert or a replace, on every run; mdl 0 rebuilt
the flow. On CapTrack this left a stub-then-real pair with the placeholder
stored.

The handler is built by a child flowBuilder whose returnEndIDs never reached
the parent, so cutFragment took the handler's return end event for one the
builder added. addErrorHandlerFlow now carries them up: the return is a new
end event of the flow, placed where the builder drew it relative to the
fragment, like a guard clause's (#888), and checkRoom/checkBranches refuse
where it would be drawn over or across stored content.

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