Skip to content

Sync ako/mxcli: SEC012 predicts mxbuild's CE2729; create or replace can clear a data grid column caption - #1715

Merged
ako merged 16 commits into
mendixlabs:mainfrom
ako:main
Nov 2, 2026
Merged

ako merged 16 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 3 commits from fork PRs ako#1875 and ako#1878.

SEC012: report mxbuild's CE2729 before a build

New lint error SEC012 predicts CE2729: a page that a user role can reach shows an attribute, or lists an entity, that the role may not read.

CE2729 "No read access to attribute 'Title' in entity 'Probe.Ticket' for user role 'ProbeUser' …"
CE2729 "No access to entity 'Probe.Ticket2' for user role 'ProbeUser' …"

Before, check --references, exec and lint passed these projects, and only a build reported the error.

Reachability was measured against mxbuild, in four rounds on fresh 11.15 and 11.13 apps:

Route to the page CE2729?
menu item, show-page button, button inside a snippet yes
microflow or nanoflow the role may execute → show page, including through a sub-microflow with no allowed roles yes
role-based home page; guest role with guest access off yes
microflow the role may not execute → show page no
menu page the role may not view; page nothing references no
page with a URL that nothing links to varies, so it is not treated as reached

An inherited attribute counts against the specialization's rules, and a role with no rule at all gets the entity error besides the attribute error.

Checked on real projects:

  • With one access-less user role added, mxbuild reports 29 CE2729 and SEC012 reports the same 27 entity and attribute errors. The other 2 are association reads, which are out of scope.
  • On two real projects as they are, where mxbuild reports no CE2729, SEC012 reports none.

Tests: one test per measured case, each with a control.

Not covered yet: association reads, System entities, Marketplace modules, layouts' own menus, and URL-only pages.

create or replace can clear a data grid column caption

The problem: create or replace page or create or modify with Caption: '' over a Data grid 2 column stored as 'Triage' reported "Unchanged page" and kept 'Triage'.

The cause: the text carry, which keeps Studio Pro's values for text properties MDL has no keyword for, put the stored caption back.

The fix:

  • An empty column header is now written as one empty translation in the authoring language, which the carry leaves empty.
  • A rebuilt empty translation in a language the stored text has none in is dropped. Studio Pro stores an empty caption with no translation at all, so without this every rewrite of such a page would churn.

Verified:

  • On 11.15, the caption change now reports "Replaced", and a second run reports "Unchanged". mx check gives 0 errors.
  • describe → exec of three Studio Pro-authored data grid pages gives the same result with the old and new builds.
  • Controls cover both the writer and the carry

ako and others added 16 commits November 1, 2026 19:34
A page a user role can reach that shows an attribute, or lists an entity, the
role may not read is CE2729 at build time; check, exec and lint all passed it.

Reachability is per user role and measured against mxbuild (11.15.0, 11.13.0,
Production): menu items the role may view; its home page (role-based, else the
profile's), checked even when the role may not view it but not walked further;
then show-page actions, snippet calls, and microflows or nanoflows the role may
execute, with their sub-microflows whatever their own allowed roles. A page
nothing reaches, a menu page the role may not view and a page behind a
microflow it may not run are not reported, as mxbuild does not.

A page reachable only by its URL was reported in fresh apps and not in
JTSBootLogboek; until the deciding condition is known it is not treated as
reached. Association reads and System entities are not covered.

Live: probe apps 6/6 and 10/10 against mxbuild; JTSBootLogboek with an
access-less role, the same 27 attribute and entity errors mxbuild reports;
FactoryManagement and JTSBootLogboek as they are, none (mxbuild: none).

Closes #936

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

On MPR v2, writing a unit A -> B -> A in one WriteTransaction (A
stored) committed B, with ContentsHash describing B, and no error.
WriteUnit reconciles each write against the stored unit (canon.Reconcile,
ADR-0008), and on v2 that was the committed .mxunit, while the
transaction's own first write sits in <unit>.mxunit.tmp until Commit.
So the second write matched the committed A, was elided as unchanged,
and Commit renamed the pending B into place. A -> B -> B counted the
repeat as a landed write for the same reason.

Latent: no current caller writes a unit twice in one transaction
(codec.Store.FlushUnits writes each map key once, SaveUnit is one
transaction per unit). WriteTransaction is public library API, though.
v1 already behaves correctly since cd0c0ba, which reads the stored
unit through the transaction.

Fix: WriteUnit passes reconcileAgainst WriteTransaction.storedBytes:
the transaction's own last write of the unit (its pending temp file),
else the read through the transaction it already did.

Verification (modelsdk/mpr/write_transaction_twice_test.go):
- v2, A -> B -> A commits A, ContentsHash is A's, WriteStats 2
  offered, 2 written.
- v2, A -> B -> B commits B, WriteStats 2 offered, 1 written.
- v1, A -> B -> A commits A. It passes on main already and pins why
  the v1 read goes through the transaction, not a second connection.
- Control, pending-file lookup disabled: both v2 tests fail, with
  "committed unit is \"B\", want A back: the second write was
  reconciled against the committed file and elided" and "WriteStats =
  2 offered, 2 written; want 2, 1".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Caption: ''` over a column whose stored caption is 'Triage' reported
"Unchanged page" and kept 'Triage', on the keyword and the generic data grid
path alike. The rebuilt empty header was a Texts$Text with no translation, and
canon.CarryTranslations adds back every language a rebuilt text did not write,
so the stored en_US 'Triage' came straight back. Neither the pluggable
passthrough nor the per-property patch was involved: switching each off
changed nothing, switching the carry off cleared the caption.

The carry cannot skip empty texts, because the same mechanism keeps Studio
Pro's values for text properties MDL cannot express. So an empty column header
is written as ONE empty translation in the authoring language (EmptyText), which
the carry treats as written; and the carry drops a rebuilt empty translation in
a language the paired stored text has none in, so a Studio Pro empty caption
(no translation at all) still rewrites as unchanged.

Verified on 11.15.0: both paths now replace and clear, a second run is
Unchanged, mx check reports 0 errors; on FactoryManagement, describe -> exec of
three data grid pages gives the same result as before the change (one with a
Studio Pro empty caption stays Unchanged).

Closes #1877

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page or snippet data source's ComponentId was its decoded $ID formatted with
%v, `{0 [57 221 206 …]}`: extractBsonIDString had no bson.Binary case. Nothing
could be joined on it, and no column named the widget the XPath belongs to
(ComponentName is the source's $Type), so lint rule SEC011 could name a widget
only when a page had one data widget over that entity.

ComponentId is now written with extractBsonID, the formatter the widgets table
uses, and the new WidgetId / WidgetName columns carry the nearest enclosing
widget (`widget_id` / `widget_name` in Starlark's xpath_expressions()).
Catalog schema 32.

Measured: on the SEC011 probe app all 6 page XPath rows join to their widget;
on FactoryManagement all 61 rows carry a GUID and a widget, 56 join, and the 5
that do not are widgets the widgets table never indexes (MxModelReflection
combo boxes and a list view) — a separate gap.

Closes #1879

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ce2729-reachable-page-read

# Conflicts:
#	CHANGELOG.md
…ce2729-reachable-page-read

# Conflicts:
#	.claude/commands/mendix/lint.md
#	.claude/skills/mendix/assess-quality/SKILL.md
#	CHANGELOG.md
#	cmd/mxcli/cmd_lint.go
#	docs-site/src/tools/builtin-rules.md
#	docs-site/src/tools/linting.md
#	mdl/linter/report.go
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SEC012: report mxbuild's CE2729 before a build
Let create or replace clear a data grid column caption
Every push to a PR branch ran the workflow twice, for the push and the
pull_request event (~102 runner-minutes each), keeping the account at its
20-concurrent-job limit for most of a busy window. Trigger on push to main,
pull_request and workflow_dispatch instead; the pull_request run is kept
because it tests the merge result.

A new push to a PR cancels that PR's in-progress run. Non-PR runs get a
concurrency group of their own (run_id): a shared group drops a queued run
even with cancel-in-progress false, since GitHub keeps one pending run per
group, and every main commit must be tested.

The steward skill described the double run; it now says a cancelled run on
an older head is expected and never blocks.

Refs #1886

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82simPxcdrRu9PUSPcQ
ci: run Build, Test & Lint once per commit and cancel superseded PR runs
Catalog: write xpath_expressions ids as GUIDs and record each row's widget
fix: MPR v2 commits the first write when a transaction writes a unit twice
@ako
ako merged commit 3c017b3 into mendixlabs:main Nov 2, 2026
15 checks passed
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.

2 participants