Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@
{"area": "cmd/mxcli", "date": "2026-09-28", "symptom": "`mxcli check x.test.mdl` reported an MDL-DEPR001 warning (`create or replace` is deprecated) on the doc comment of every @test block, for a spelling the author never wrote; the conformance gate (#756) counted 70-odd of them across mdl-examples' test files", "cause": "testrunner.CheckSource renders each test block as a microflow so the top-level grammar can check it, and its wrapper text was `CREATE OR REPLACE MICROFLOW …`, placed on the doc-comment line. The deprecation registry landed after the wrapper, and nothing checked mxcli's own generated MDL against it", "file": "`cmd/mxcli/testrunner/check_source.go` (wrapper spelling), test `TestCheckSourceWrapperIsCanonical` in `check_source_test.go`", "insight": "**Generated MDL is subject to the deprecation registry too**, and a warning on generated text lands on the author's line numbers, where it looks like their mistake. The other generators in testrunner (generator.go, endpoint.go) still write `create or replace` into scripts that are executed, not checked, so they warn nowhere a user sees — left alone. Proof: the new test fails with MDL-DEPR001 on line 1 of the rendering before the one-word fix.", "refs": ["ako/mxcli#756"]}
{"date": "2026-09-30", "area": "cmd/mxcli", "symptom": "`mxcli fmt --upgrade tests/csv-import.test.mdl` failed with `no viable alternative at input '/**\\n * @test …'` on a file `mxcli check` passes, so the migration tool could not reach a project's test suite; separately `mxcli check x.test.md` reported a syntax error on every valid markdown test block whose doc comment spans lines.", "cause": "fmt parsed the file as top-level MDL, but a test file is `/** @test */` doc comments and `/` separators around microflow bodies; only check rendered it (testrunner.CheckSource). The markdown parser counted a fence block's lines from the ```mdl-test line instead of the line after it, so BodyLine was one early and the rendering overwrote the doc comment's last line with the body.", "fix": "testrunner.UpgradeSource upgrades CheckSource's line-preserving rendering and maps it back line for line: a line the rendering kept verbatim takes the upgraded text, every other line stays the author's, and a rewrite reaching a wrapper line or changing the line count is refused. fmt routes test files there (plain fmt on one is refused with a pointer to --upgrade). parseMarkdownTests passes blockStart+1 as the chunk line.", "insight": "A test file takes no language header: neither check's rendering nor either runner generator reads a `mdl 1;` line, and one in front of the first @test makes that test's doc comment non-leading, so the runner silently drops it. --header therefore adds no header to a test file and says so, rather than applying mdl 1 rewrites the runner would execute as mdl 0. The markdown BodyLine bug was found only because the upgrade's corpus test re-parsed the rendering of mdl-examples/doctype-tests/microflow-spec.test.md; it also shrank the conformance allowlist.", "issue": "ako/mxcli#837", "file": "cmd/mxcli/testrunner/upgrade_source.go, cmd/mxcli/testrunner/parser.go (parseMarkdownTests), cmd/mxcli/cmd_fmt.go", "test": "cmd/mxcli/testrunner/upgrade_source_test.go, TestCheckSourceMarkdownBodyLine, cmd/mxcli/cmd_fmt_upgrade_test.go TestFmtUpgrade_TestFile"}
{"date": "2026-10-01", "area": "cmd/mxcli", "symptom": "The LSP's CREATE MICROFLOW / CREATE NANOFLOW / CREATE ENUMERATION snippet completions inserted MDL that does not parse under any language version (`missing '(' at 'BEGIN'`, a quoted enumeration value name), the CONSTANT snippet the deprecated clause form (MDL-DEPR136), and PAGE/SNIPPET a statement mdl 1 refuses for its missing `;`; completion also offered alias-only keywords (SHOW_PAGE, DELETE_BEHAVIOR, DEFINE) and the `show entities` listings after SHOW.", "cause": "The snippets and keyword lists were hand-written once and never parsed; the grammar moved on (R2 property lists, R8 words, `list` for `show`) and nothing tied the completion text to it. The keyword list is generated from the lexer alone, which cannot tell an alias token from a canonical one: that is said by the `/* @alias MDL-DEPRnnn */` markers in the parser grammar.", "fix": "Snippets rewritten to canonical mdl 1. cmd/gen-completions reads the parser grammar too and drops a token every parser-rule use of which carries an @alias marker (the `keyword` rule, which lists tokens usable as names, does not count). `list` gets the listings; `show` offers page/message/home page.", "insight": "A completion text is MDL that ships in the binary, so it is held to what docs are held to: TestCompletionSnippetsAreMdl1 parses every snippet, expanded with its defaults, under `mdl 1;` and requires no deprecation. Deciding alias-only from the markers is the registry's own data; a token-swap rewrite word (`snippet`, `column`, `comment`) is NOT alias-only \u2014 those words stay canonical elsewhere \u2014 and neither is SHOW (show page, show message).", "issue": "ako/mxcli#714 (decision 5)", "file": "cmd/mxcli/lsp_completion.go, cmd/gen-completions/aliases.go", "test": "cmd/mxcli/lsp_mdl1_test.go TestCompletionSnippetsAreMdl1, TestCompletionOffersNoDeprecatedSpelling, TestCompletionListAndShowContinueIntoMdl1; cmd/gen-completions/aliases_test.go"}
{"date": "2026-10-01", "area": "cmd/mxcli", "symptom": "A stub-then-real script set (two files each with `create or modify microflow X`) upgraded with `fmt --upgrade -p` one file at a time ended up split: the stub's file declined the header (MDL-V1-REBUILD) and the real flow's file took it. Run in order, the mdl 0 stub rebuilt the stored real flow and the mdl 1 real statement was refused, every run; mx check 0 errors, the app running the placeholder.", "cause": "Each command judged one file against the model as stored; check/fmt took exactly one file, so nothing could see a flow declared twice across the set, and a per-file header decision is wrong for a pair whose files must agree.", "fix": "check and fmt accept several files as one script set (cmd/mxcli/script_set.go): findFlowRedeclarations finds a flow declared by two create-or-modify statements; check warns MDL-STUB01 naming both; fmt --upgrade groups files sharing such a flow and declines the header for all of them when any one cannot take it (canTakeHeader = the per-file verdict). One file alone is unchanged.", "insight": "A per-file verdict is only right when the files are independent; the run is the unit when two files write the same document. The symptom hides because each half is individually correct (decline is right for the stub, header is right for the real file) - only the combination is wrong, so test the set, with the single-file decision as the control.", "issue": "ako/mxcli#905", "file": "cmd/mxcli/script_set.go", "test": "cmd/mxcli/script_set_test.go TestFmtUpgrade_ScriptSetDecidesStubThenRealHeaderTogether, TestCheck_ScriptSetWarnsOnStubThenReal"}
{"date": "2026-10-01", "area": "cmd/mxcli", "symptom": "fmt --upgrade over a stub-then-real set still split the pair when the stub's file carried a written `mdl 0;`: the real file took `mdl 1;`. With the real file already `mdl 1;`, fmt printed 'no language header added' about it while the pair stayed split.", "cause": "canTakeHeader returned true for ANY written header (langver.ScanWrittenHeader's bool), reading a pinned `mdl 0;` as 'already has the header'; and the group decline was applied to a file that already carries the header, which fmt never removes.", "fix": "canTakeHeader: a written header can take it only when it is langver.Latest. decideSetHeaders: a file already under the header is not declined; fmt says the pair stays under different headers and to drop the stub or take the header off.", "insight": "ScanWrittenHeader's bool means 'a header is written', not 'the header is mdl 1'; a written mdl 0 pin is the strongest 'cannot take it' there is. Test the group decision with every header state of each file, not only headerless ones.", "issue": "ako/mxcli#905", "file": "cmd/mxcli/cmd_fmt.go", "test": "cmd/mxcli/script_set_test.go TestFmtUpgrade_ScriptSetPinnedMdl0StubHoldsTheRealFileBack, TestFmtUpgrade_ScriptSetAlreadySplitPairIsReportedAsSplit"}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,4 @@
{"date": "2026-10-01", "area": "mdl/executor", "symptom": "ako/mxcli#887 (rehearsal 2, N1): `create or modify microflow|nanoflow` of a stored, foldered flow with NO folder clause moved it to the module root (`Moved microflow: ...`), both language versions; `mxcli diff` said 'moved to the module root'. With a separate organise step (`move ... to folder`) every run moved the flows out and back: 19 flows a run in mxcli-rest, 47-55 in formula1, the .mpr rewritten each time although no unit's content changed.", "cause": "The splice path (modifyFlowInPlace) compared the declared folder with the stored folder and moved on any difference, treating an empty clause as 'the module root'. Every other create-or-modify path follows document_placement.go's rule that no folder clause leaves the document where it is (containerForDocument / resolveRequestedFolder, or `if s.Folder == \"\" { container = existing }`); the flow rebuild path did too. spliceVerdict in cmd_diff.go copied the same comparison.", "file": "mdl/executor/cmd_flow_modify.go, mdl/executor/cmd_diff.go", "fix": "movesFolder(declared, stored) = declared != \"\" && declared != stored, used by modifyFlowInPlace (which now resolves via resolveRequestedFolder) and by spliceVerdict. Audited every resolveFolder/applyDocumentFolder caller (pages, snippets, rules, enumerations, constants, json structures, mappings, message definitions, business events, published/consumed REST, OData, agent-editor documents, workflows, queues, ...): all already keep the stored container when the clause is absent.", "test": "mdl/roundtrip/flow_modify_folder_test.go TestSpliceRerun_NoFolderClauseKeepsTheFlowFiled (integration, parity suite; Studio Pro-authored PedApp Administration.ChangeMyPassword and Atlas_Web_Content.DS_LoginContext, both headers: no Moved report, folder kept, exec 2 writes nothing, organised re-run leaves the .mpr byte-identical, diff reports no move; control: a folder clause naming another folder still moves). Revert check: all four subject/header pairs fail with 'moved the flow out of' and the organised re-run rewrites the .mpr.", "insight": "A move that is undone in the same run is invisible to a unit snapshot (contents and final container are the same) and does not move LastTransactionID; only the .mpr bytes show it. When a second path implements a rule a helper already encodes (no clause = leave it), route it through the helper rather than re-deriving the comparison."}
{"date": "2026-10-01", "area": "mdl/executor", "symptom": "ako/mxcli#890 (rehearsal 2, R-rep): re-running a settled script wrote nothing (units_written=0) but still printed \"Granted access on ...\", \"Set project security level to ...\", \"Added module roles ... to user role ...\", \"Updated ... settings\" / \"Updated configuration ...\", and `move ... to folder` printed \"Moved ... to new location\"; the output could not serve as the #859 'second run reports Unchanged' gate. Also: a second `move` of the same document in one session failed \"microflow not found\".", "cause": "Those handlers printed their sentence with fmt.Fprintf after the backend call instead of going through ReportMutation's write-elision evidence (WriteStats offered vs written). Grants/revokes inside a program run are deferred (#872 accessRuleRun), so even ReportMutation could not see their write at statement time. The typed movers changed a container without invalidating the cached hierarchy.", "file": "mdl/executor/report_mutation.go, mdl/executor/access_rule_run.go, mdl/executor/cmd_security_write.go, mdl/executor/cmd_settings.go, mdl/executor/cmd_move.go", "fix": "ExecContext.reportWrite(unchanged, sentence...) prints the sentence or `Unchanged <subject>` (through the run tally) on the ReportMutation evidence rule; used for project security level/demo users/strict mode/guest access, alter user role module roles, settings section/configuration/constant updates. Access-rule reports go through reportAccessRule: held on the open accessRuleRun and printed at its flush, Unchanged when the flush offered and elided (notices like 'No access rules found' print regardless). execMove short-circuits a document already in the target container (alreadyPlaced -> Unchanged) and invalidates the hierarchy after every move.", "test": "mdl/executor/noop_reporting_pedapp_test.go TestNoopRerun_ReportsUnchanged (PedApp, per statement: run 1 reports its write = control; run 2 writes no file and reports Unchanged, for grant, security level, demo users, strict mode, user role module roles, settings runtime, configuration (alter and create or modify), move) and TestNoopRerun_ProgramReportsUnchanged (program run incl. a revoke+grant reset; run 2 writes nothing and reports no write verb; run 1's net-nothing reset reports no write). Revert check: every case fails with the write sentence on run 2; the move case with 'microflow not found'.", "insight": "A report printed after a backend call is a claim about storage the handler cannot make on its own; route every write report through the write-stats evidence, and where writes are deferred, defer the report with them. The output only becomes an idempotency gate when no statement prints a write verb by construction."}
{"date": "2026-10-01", "area": "mdl/executor", "symptom": "ako/mxcli#840 (mxcli-ledger, finding 162): `mxcli describe` (no header, no option) wrote mdl 0 spellings that mdl 1 refuses, and its microflow output held `$N = count($Hits)` / `$x = find($L, …)` — call forms registered as deprecated (MDL-DEPR003/004), so subcommand output was neither mdl 1-runnable nor mdl 0-clean. At the freeze a second instance surfaced on TestApp: describe of a chart series' text template wrote `staticTooltipHoverTextParams: [{1} = X]`, the bracketed form MDL-DEPR124 deprecates, under both versions.", "cause": "formatListOperation / the AggregateListAction case gated the statement form on describeLanguage >= V1 and fell back to the call form for mdl 0, although the statement form parses with the same meaning and no warning under mdl 0. The object-list describer (cmd_pages_describe_objectlist.go) wrote a TextTemplate's <Name>Params as \"[\" + … + \"]\". The roundtrip test that should have caught both (describeUsesCanonicalSpellings) filtered deprecations to an R8 allowlist ('describe keeps them under mdl 0'), so any code outside the list was invisible.", "file": "mdl/executor/cmd_microflows_format_action.go, mdl/executor/cmd_pages_describe_objectlist.go, mdl/roundtrip/describe_canonical_spelling_test.go", "fix": "Describe writes the List operation / Aggregate list statement in every language; only an activity the statement cannot express falls back to the call. Object-list template parameters are written in ( ). The canonical-spelling roundtrip test now checks EVERY registered deprecation under both describe languages (describeAs V1 and V0) on PedApp and TestApp, with no code filter. Freeze: langver.Frozen = V1, every describe output starts with `mdl 1;`, `--mdl 0|1` on describe/context/diff-local.", "test": "mdl/executor/cmd_microflows_format_list_activity_test.go TestDescribeListActivityUnderMdl1 (both versions, plus the call-form control warning under mdl 0); mdl/roundtrip/describe_canonical_spelling_test.go TestTestAppDescribeUsesCanonicalSpellings (failed on Snip_TaskDashboard_Numbers & 2 more with MDL-DEPR124 before the objectlist fix).", "insight": "A test that filters warnings to a list of 'codes this test owns' hides every code added later; a 'never emits X' property must check the whole registry, and in every output language the command can be asked for."}
{"date": "2026-10-01", "area": "mdl/executor", "symptom": "ako/mxcli#905 part 1 (#897 item 4, rehearsal 3 G2): under `mdl 1`, `create or modify microflow` that grows a stored flow by an activity whose custom error handler ends in its own `return` (`$Ok = call microflow … on error begin log …; return; end error;`) was refused on every run, as an insert or a replace: \"an error handler in the fragment ends at an end event of its own … a return inside an error handler is not spliced yet\". Created fresh the statement worked; mdl 0 rebuilt (MDL-V1-REBUILD). On CapTrack the stub-then-real pair (13-actions stub, 30-export real ACT_Export_Excel) left the placeholder stored on every run with mx check clean. Once spliced, the second run of the real CapTrack script was refused: \"replace $Written: cannot replace the ActionActivity …: it has an error handler\".", "cause": "addErrorHandlerFlow (cmd_microflows_builder_flows.go) builds a handler body with a child flowBuilder and merged its objects and flows into the parent, but not its returnEndIDs, so cutFragment saw the handler's return end event as one the builder added and refused it. Second defect, exposed by the grow: a log/show message/validation feedback message written as an expression (`'failed for ' + $User/Name`) is stored as template '{1}' with the expression as parameter, and describe prints that form; declaredMatches compared the two spellings as different statements. Where builtAsStored is false (a spliced or Studio Pro-drawn flow), the statement diff then replaced the activity: absorbed by write elision on the main path, but refused when the message sits in a stored activity's error handler.", "file": "mdl/executor/cmd_microflows_builder_flows.go, mdl/executor/flow_declared_match.go", "fix": "addErrorHandlerFlow copies errBuilder.returnEndIDs into the parent's (lastReturnEndID untouched), so a handler's return is a new end event of the flow like a guard's (#888); placement/room checks (checkRoom/checkBranches) apply unchanged and refuse where the handler's return branch would cross a stored flow. matchValue normalises LogStmt/ShowMessageStmt/ValidationFeedbackStmt (messageAsTemplate) to the builder's stored form: a non-literal message becomes '{1}' with the expression as first parameter (a log stating its own `with (...)` keeps its message, as the builder does).", "test": "mdl/executor/cmd_alter_flow_handler_return_test.go TestCutFragment_HandlerReturnIsANewEndEvent; mdl/executor/flow_message_respelling_test.go (with controls); mdl/roundtrip/flow_splice_handler_return_test.go TestSpliceRerun_GrowByHandlerReturn (replace/insert under mdl 1, insert under mdl 0, verdict agreement check/diff/exec, twice-exec, a changed-flow control), TestSpliceRerun_GrowStudioProFlowByHandlerReturn (PedApp ShowPasswordForm, all stored IDs kept, description fixed point), TestSpliceRerun_HandlerReturnWithNoRoomIsRefused. Revert checks: returnEndIDs not copied -> every grow refused 'not spliced yet' (check predicts it); messageAsTemplate off -> second run refused 'replace $Ok … it has an error handler'. CapTrack copy: fmt --upgrade -p 13+30, exec 13, 30, 30 -> real flow stored, third run 0 units; mx check identical to baseline (0 errors). PedApp repros mx check identical to baseline.", "insight": "Child builders (error handler, loop) each keep builder state the parent's consumers rely on; when a new piece of builder state is added for the splice (returnEndIDs, #888), enumerate the child builders and decide per child whether it propagates. A grow test that only re-runs on an mxcli-authored flow can pass because builtAsStored short-circuits the statement diff; the respelling only surfaced on the real project and the Studio Pro-drawn flow, where the statement diff decides."}
Loading
Loading