Skip to content

new(mdl-scan): flag scannable STOP-row patterns before the apply, warn-only until a field run (#349) - #355

Closed
MendixMau wants to merge 2 commits into
masterfrom
feat/mdl-static-scan
Closed

MendixMau wants to merge 2 commits into
masterfrom
feat/mdl-static-scan

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Closes #349.

Review (independent review: MERGE-AFTER-FIX, addressed in 870c5b5)

This ships warn-only. All four rules are severity warn. exec.sh prints each hit as a warning, adds scan warn: STOP-… to the BUILD-LOG row, and goes on.

What is still owed before any rule blocks: one field run of exec.sh on a real project, in which the rule caught a real failure and flagged nothing valid. That run is cited on the commit that flips the rule's severity to block. The criterion lives in the header of bin/lib/mdl-scan-rules.tsv.

  • Candidates for block: STOP-12, STOP-16 and STOP-26.
  • STOP-1 stays warn. Its StorageLoadException evidence is from v0.12–v0.13; on v0.24.0 mx check is 0 errors and Studio Pro load is untested.

Fixes:

  1. STOP-12 false hit (HIGH). A ContentParams list now ends at its matching ], found by bracket depth in the string-blanked line, and code is cut at that offset. Before, it ended at the last ] on the line.
    • dynamictext t (Content:'{1}', ContentParams: [{1} = Name], Visible: [IsActive]) is now clean.
    • The multi-line form closed by ], Visible: [...] is now clean.
    • Both forms are fixture cases (L) and are in the clean corpus.
  2. STOP-1 false hit (HIGH). A customContent column with no body is now forgotten at the next }, or at the ) that closes its header's scope. When such a column was the last column, a later nested container (Visible: [...]) was flagged. It is now a fixture case and is in the clean corpus. I confirmed that the previous scanner flags both review forms.
  3. Severity column. mdl-scan-rules.tsv gains a severity column (warn|block).
    • Scan exit codes: 1 = at least one block hit, 3 = warn hits only, 0 clean, 2 usage.
    • Output lines end in [warn] or [block].
    • The block path is kept: refuse before lock and snapshot, FORCE_EXEC/SKIP_CHECK do not skip it, and MXTK_SCAN_OVERRIDE="<reason>" is the override. The fixture exercises it through a test-only rules file with STOP-26 set to block.
    • MXTK_SCAN_OVERRIDE now matters only for block rules. A fixture check (W) asserts that every shipped rule is warn.
  4. Low. Rule kind customcontent now matches the string-blanked line. Fixture case: 'visible: [never]' inside a column body is clean.
  5. Changelog now also records the STOP-16 example rewrite (generic entity names).
  6. Bench re-run with the review's cases in the clean corpus: 0 / 19 false positives (was 0 / 17).

Direct lane

What changed and why: skills/learned-mdl-preflight.md's STOP table is prose, and an agent can skip it. bin/mdl-scan.sh <script.mdl> [-p <mpr>] turns each STOP row that a text pattern identifies, and that has a captured failure, into one data row in bin/lib/mdl-scan-rules.tsv.

  • Output: STOP-<n> <file>:<line> <message> → <fix> [warn|block].
  • Wiring: project-bin/exec.sh runs the scan first, before the lock, --commit-first, mxcli check and the snapshot.
  • Override: there is no Lock gate baselines against the agent: re-baselining is a human call #348 mechanism yet, so the block override is MXTK_SCAN_OVERRIDE="<reason>", meant for the user. A bare 1/true/yes is refused, and the reason is printed and logged. Lock gate baselines against the agent: re-baselining is a human call #348 will harden it.
  • When the scan cannot run: if the toolkit is not found, or the scan exits 2, exec.sh prints one warning and goes on.
  • Preflight marking: learned-mdl-preflight.md marks the enforced rows with 🔎 and gains a one-line legend.
  • Word cost: baseline 75,028 → 75,034 words (+6; budget 80,000).
  • Row 16 example: it now uses generic entity names in place of project-specific ones. The verbatim form stays in the bug log.

Classification of every STOP row (denominator: 29 numbered rows, 14 live, 15 retired)

# Row Class In scanner?
1 conditional visible:/editable: inside a datagrid customContent column (a) scannable (bracket-depth-aware) yes, BUG-18 (warn, stays warn)
1b quoted Editable: '<expr>' in CREATE PAGE (a) no, proposed/unproven (silent drop; no captured output in bug-logs)
1c, 2, 3 retired n/a
4 bind AutoChangedBy (a) no, proposed/unproven (CE1613 quoted only in the row; BUG-35 is a different, Engalar-only grammar issue)
5, 6, 6b, 7, 8, 9, 10, 11, 13, 15, 17, 27 retired n/a
12 expression in ContentParams (a) yes, BUG-142 (warn, block candidate)
14 narrowing an existing grant (b) model state no
16 path inside [%CurrentUser/…%] (a) yes, CE0161 entry "Any GRANT/REVOKE exec … Account_Supplier-style …" (warn, block candidate)
18 workflow calls a microflow created in the same script (a) cross-statement no, proposed/unproven (CE0117 only in the row)
21 DROP+CREATE a document another script edits (b) other scripts / judgement no
22 non-idempotent statements (b) fails only if the name exists (model state) no
23 create or modify on a name that may exist (b) mxcli diff + judgement no
24 DesignProperties the theme does not declare (a) with theme files no, proposed/unproven (CE6083 only in the row)
25 microflow with loops or >20 activities (b) process step no
26 quoted association path in DG2 column Attribute: (a) yes, BUG-DRAFT-dg2-association-path-bindings (warn, block candidate)
28 change near return in a dense flow (b) model layout no

No shipped rule uses -p. The flag is accepted and validated.

Proposed, unproven (not in the scanner): STOP-1b, 4, 18 and 24, plus the non-STOP gotchas the bench misses: quoted $Var/"Attr", hasRole(), and currentDeviceType() in a nanoflow.

Before/after (every number computed by tests/wave2/bench-mdl-scan.sh, re-run after the review)

measure before (no scan) after (mdl-scan)
failing corpus flagged before apply 0 / 9 4 / 9 (44%)
of those, refused by a block rule / warned by a warn rule — 0 / 4
failing scripts with no signal before the apply 9 5 (missed: BUG-75.dynamic-class BUG-77.quoted-member BUG-78.refset-literal BUG-86.device-type-nanoflow BUG-94.hasrole)
false positives on clean corpus — 0 / 19 files (0%), 799 lines
scan time per 1,000 lines — 26.6 ms (best of 5 over 10276 lines)
apply time spent on flagged failures 88–260 s 0 s once each flag is acted on
estimated time saved — 88–260 s = 4 × 22–65 s per apply, restore not counted
  • Warn-only caveat: a warn hit saves the apply only when the agent fixes the script on the warning; a block hit always does.
  • Apply cost: read by the script from iterative-build-loop.md → "Gate cost" (field run 2026-09-29).
  • Failing corpus: captured bad MDL from open bug-log entries that pass mxcli check and fail at mxbuild or Studio Pro open. Each file's CE text is asserted verbatim in bug-logs/mxcli-bugs.md.
  • Clean corpus: examples/stylegallery (0 errors in mx check), the corrected forms, and the review's cases.
  • Scan time: it varies with machine load (9–27 ms across runs on a shared box). A 2,000-line script stays well under 1 s.

Field evidence: captured bug-log outputs only, no live run. That is why everything ships warn-only. For each rule, the fixture holds the bad MDL captured in its bug entry (-- key:) and the CE/mxbuild output that entry cites (-- evidence:), both asserted verbatim in the ledger.

  • No client data anywhere in the diff (leak guard clean; blocklist grep of added lines clean)

  • Size cap: 8 non-fixture files. Over 400 lines once the test and bench scripts count. Fixtures are excluded.

  • Test tier: T1, fixtures only. Against this branch's exec.sh:

    • test-mdl-scan.sh 52/52
    • test-exec-change-class 21/21
    • test-exec-lock-watch 84/84
    • test-bug07-08 43/43
    • test-exec-approval 43/43
    • test-shared-lock-takeover 6/6

    Positive control: the first revision of test-mdl-scan.sh failed 8 exec checks against master's exec.sh.

  • Golden input captured from the bug log (verbatim-asserted). Bash 3.2 + POSIX awk; check-portability clean. Not run on macOS or Git Bash.

  • CHANGELOG.md line, credited

  • n/a new skill or bug entries

Checks: render-routing --check in sync, check-scripts 111/111, check-portability clean, check-no-client-data clean, check-no-private-citations clean, check-pr-discipline clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw

)

bin/mdl-scan.sh greps an MDL script for the STOP rows of
learned-mdl-preflight.md that a text pattern identifies and that have a
captured failure in bug-logs/: rows 1 (BUG-18), 12 (BUG-142), 16 (CE0161
CurrentUser path) and 26 (dg2 quoted association path). Rules are data in
bin/lib/mdl-scan-rules.tsv. exec.sh runs it first, before the lock, the
commit, mxcli check and the snapshot; a hit refuses the exec. The only
override is MXTK_SCAN_OVERRIDE="<reason>", printed and logged (#348 will
harden it).

Fixtures: tests/wave2/test-mdl-scan.sh (43 checks, keys and CE text
asserted verbatim in the ledger), tests/wave2/bench-mdl-scan.sh (4/9
captured failures caught, 0/17 clean files flagged).

Field evidence: captured bug-log output only; no live mxcli/mxbuild run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…fixed

- STOP-12: a ContentParams list now ends at its matching ] by depth (in the
  string-blanked line), not at the last ] on the line, so
  `ContentParams: [{1} = Name], Visible: [IsActive]` (one line or two) is clean.
- STOP-1: a body-less customContent column is forgotten at the next } or at
  the ) that closes its header's scope; a LAST such column no longer marks a
  later container as inside it. customcontent rules match the line with
  string literals blanked.
- Severity column (warn|block) in mdl-scan-rules.tsv. Every rule ships warn
  (scan exit 3): exec.sh prints the hits, notes them in BUILD-LOG, goes on.
  The block path (exit 1, refuse, MXTK_SCAN_OVERRIDE) is kept and tested
  through a test-only rules file. Flip criterion in the rules-file header.
- Clean corpus gains the review's forms; bench reports 0/19 false positives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau MendixMau changed the title new(mdl-scan): refuse scannable STOP-row patterns before the apply (#349) new(mdl-scan): flag scannable STOP-row patterns before the apply, warn-only until a field run (#349) Oct 14, 2026
MendixMau pushed a commit that referenced this pull request Oct 14, 2026
…e apply, warn-only until a field run (#349)

Review: independent review MERGE-AFTER-FIX — two false hits on valid MDL (STOP-12 last-] cut; STOP-1 pending not cleared), STOP-1 evidence predates v0.24, no field run → bracket-depth and pending fixes, severity column, all four rules ship warn-only (blocking path tested via a test-only rules file); confirmation MERGE at 870c5b5: six new valid-MDL cases clean, warn-only cannot stop an exec (exit 3 guarded from set -e), BUILD-LOG readers unaffected. Bench: 4/9 captured failures caught, 0/19 false positives, ~10–30 ms per 1,000 lines. A rule flips to block only after a cited field run. Closes #349.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau added a commit that referenced this pull request Oct 15, 2026
…journey controls, fixes) (#385)

Lands #340 #363 #362 #355 #370 #368 #364 #365 #366 #372 #367 #371 #376 #369 #373 #377 #378 #380.

Review: each constituent PR independently reviewed by a fresh agent that did not write it; merged into integration/2026-10-15 only on a MERGE verdict with green CI (per-PR Review lines in the integration merge commits). After-fix rounds: #373 (env-var cap, rename/skip-loops freshness), #377 (inline URI fallback), #378 (full-pack double count, slice content). Stub diet (#369/#378/#380) behaviour A/B, 5 reps x 2 drafting tasks + 5 retrieval probes x 3 reps: trimmed context no worse than master on every scored metric (planted STOP refused 20/20, parse failures page 2/5 vs 3/5, probes 15/15); mdl Stage 5 56,236 -> 30,647 words, baseline 75,028 -> 73,233. Full suites on c9bc073: run-tests 33/0, wave2 111 fixtures 0 failing. Field evidence: none yet for the instruments (#355 warn-only, #373/#377 read-only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau

Copy link
Copy Markdown
Owner Author

Landed on master in #385 (squash 868b3cb), after its own independent review on the night integration branch. Closing this PR.


Generated by Claude Code

@MendixMau MendixMau closed this Oct 15, 2026
@MendixMau
MendixMau deleted the feat/mdl-static-scan branch October 15, 2026 10:45
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.

Turn the learned-mdl-preflight STOP table into a static pre-exec scan

2 participants