Skip to content

fix(diff-local): show settings and access-rule changes, and never count a change silently - #1745

Merged
ako merged 2 commits into
mainfrom
fix/1512-diff-local-silent-modified
Oct 27, 2026
Merged

ako merged 2 commits into
mainfrom
fix/1512-diff-local-silent-modified

Conversation

@ako

@ako ako commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Closes #1512 (mirror of mendixlabs#1602). Closes #1562 (mirror of mendixlabs#1630).

diff-local counted a unit as modified and printed nothing for it. One commit per issue; the #1512 commit also carries the class-level fix.

#1512 — a project-settings change prints nothing

Cause: bsonToMDL (cmd_diff_local.go) had no case for Settings$ProjectSettings, so the unit rendered as two comment lines that read the same at every revision. Unified printed nothing, side printed an identical table, struct printed "(no changes)".

Fix:

  • The settings unit renders with describe settings' own writer (writeProjectSettingsMDL, split out of describeSettings). The old revision is parsed by a new backend method, ParseProjectSettingsFromRaw; mock stub returns "not configured", MCP returns unsupported.
  • Configuration constant values are masked, as the issue asks, because they often hold secrets. Each side is rendered knowing the other's value, so a change still shows as a -/+ pair (<masked: old value> / <masked: new value>); an unchanged value is <masked>. The mask is not a string literal, so pasted output fails to parse instead of storing the text.
  • Class fix: git has already said the bytes differ, so a modified unit whose two renderings are equal now prints one line, in every format, naming the changed property paths (never values), e.g. Modified: ModuleSecurity M: changed in properties the MDL rendering does not show: ModuleRoles (0 -> 1 items). diff-local now prints through WriteDiffResults, the writer mxcli diff already used, and the path walker moved from mdl/scriptdiff to executor.ChangedPaths so both commands share it. The walker no longer counts Mendix's leading list marker as an item.

#1562 — a grant on an entity prints nothing

Cause: domainModelBsonToMDL rendered attributes and associations but never AccessRules.
Fix: each entity is followed by its rules, written by outputEntityAccessGrants, the writer describe entity uses, from a parse of that revision (new backend method ParseDomainModelFromRaw). If the parse fails, a comment line says the rules are not shown.

Verified

  • Tests drive diff-local through the real modelsdk backend on a copy of the v2 fixture (cmd_diff_local_unrendered_test.go, cmd_diff_local_access_rules_test.go), plus raw-parse vs stored-read tests for both new backend methods. Controls: a re-run that writes nothing reports "No local changes found"; an attribute change renders byte for byte as before.
  • Revert proofs: without the settings case and the note the output is exactly the reported Summary: 0 new, 1 modified, 0 deleted; without the mask the value 'secret123' is printed; without the grant rendering the grant shows only as the note.
  • Real 11.13 app in git: before, Summary: 0 new, 2 modified, 0 deleted and nothing else; after, the masked constant pair, the BcryptCost hunk and -grant read * … / +grant read *, write * …. Range mode (--ref HEAD~1..HEAD) gives the same; mx check 0 errors.
  • Probe of other unit types, all silent before and now printing the note: navigation menu item, app security, user role, module role, page grant, published REST grant, constant default, entity index.
  • Full go test ./..., go vet, gofmt and CI's doc checks pass with all five of this batch's branches merged onto main. Bug-tests difflocal-1512-settings-silent-modified.mdl, difflocal-1562-entity-grant-silent-modified.mdl; findings recorded; diff-local command doc and docs-site updated.
  • Not verified: the MCP backend path (the new methods return unsupported there, so the unit falls back to the note).

Noticed, not fixed

  1. diff-local runs git diff in the process's working directory, not the project's repo, so it fails from outside the repo.
  2. Struct and side formats align the two sides by line number (compareGeneric): one inserted line marks every following line as changed.
  3. Project-level units (settings, navigation, project security) are named by their unit UUID in headers and notes.
  4. A unit with both a rendered and an unrendered change shows only the rendered hunk.
  5. Projects$ModuleSettings and Security$ModuleSecurity dispatch to moduleBsonToMDL (their type names contain "Module") and render as create module ;.
  6. The durable fix is for diff-local to stop keeping its own renderers: check the old revision out into a scratch folder and describe both sides, as mxcli diff does.

🤖 Generated with Claude Code

ako and others added 2 commits October 27, 2026 15:22
…ng (#1512)

After `alter settings constant … in configuration …` or `alter settings
runtime (BcryptCost: 11)`, `mxcli diff-local` printed only
"Summary: 0 new, 1 modified, 0 deleted", and `--format struct` said
"(no changes)" (mendixlabs#1602).

Cause: bsonToMDL had no case for Settings$ProjectSettings, so the unit
fell to the generic rendering — two comment lines naming the type, the
same at every revision. The same silence hit every unit whose change lies
outside what its renderer prints: a probe of one statement per kind on a
git-initialised 11.13 app found navigation, project security (admin user,
user roles), module security, a page's and a published REST service's
allowed roles, an entity index and a constant's default value all counted
and blank.

Fix:
- The settings unit renders with DESCRIBE SETTINGS' own writer
  (writeProjectSettingsMDL, split out of describeSettings) over a parse of
  each revision (new Backend.ParseProjectSettingsFromRaw, RawParts
  included, so the stored-property filter matches describe). Configuration
  constant values are masked, as the issue asks: each side is rendered
  knowing the other's value, so a change still shows as
  `value <masked: old value>` / `value <masked: new value>`; an unchanged
  or one-sided value is `<masked>`. The mask is not a string literal, so
  pasted output fails to parse instead of storing it.
- The class: git has already said the bytes differ, so a modified unit
  whose two renderings are equal is printed — in every format — as one
  line naming the property paths that changed, never their values:
  "Modified: ModuleSecurity M: changed in properties the MDL rendering
  does not show: ModuleRoles (0 -> 1 items)". diff-local now prints via
  WriteDiffResults, the writer `mxcli diff` uses for the same situation,
  and the path walker moved from mdl/scriptdiff into the executor
  (ChangedPaths) so both commands share it. The walker no longer counts
  Mendix's leading typed-array marker as a list item (a first grant read
  "1 -> 2 items"), which corrects `mxcli diff`'s note too.

Verification: real-backend tests drive diff-local over a copy of the v2
fixture with git answered from a unit snapshot — masked constant change
and addition, runtime change in unified/side/struct, the note for an
unrendered unit in all three formats; controls: an elided re-run reports
no changes, and an attribute change renders byte for byte as on
97e1cce. Revert proofs: without the settings case and the note the
tests print exactly the reported "Summary: 0 new, 1 modified, 0 deleted"
/ "(no changes)"; without the mask they print 'secret123'; without the
marker skip they read "1 -> 2 items". Real app (11.13, git): the issue's
script now shows the BcryptCost and masked-constant hunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After `grant read *, write * on entity Shop.Customer to Shop.User`,
`mxcli diff-local` counted the domain model as modified and printed no
hunk (mendixlabs#1630); `describe entity` showed the new rule.

Cause: domainModelBsonToMDL rendered each entity's attributes and the
associations from the raw BSON map, but never the entities' AccessRules,
so an access-only change rendered the same at both revisions.

Fix: each entity is now followed by its access rules, written by
outputEntityAccessGrants — the writer DESCRIBE ENTITY uses — over a parse
of that revision of the unit (new Backend.ParseDomainModelFromRaw, the
gen->semantic conversion GetDomainModel uses). No second grant renderer:
a test pins that every `grant` line `describe entity` prints, where-clause
and member lists included, appears verbatim on diff-local's new side. A
parse failure is printed as a comment rather than dropping the rules
silently.

    -grant read * on entity M.Item to M.User;
    +grant read *, write * on entity M.Item to M.User;

Verification: real-backend tests over a copy of the v2 fixture (git
answered from a unit snapshot) for a changed, an added and a revoked
rule, unified and struct; a backend test that the raw parse yields the
same access rules as GetDomainModel for every domain model in the
fixture. Control: an entity without rules still renders byte for byte as
on 97e1cce. Revert proofs: without the grant rendering the tests fail
with "Modified: DomainModel M: changed in properties the MDL rendering
does not show: Entities[Item].AccessRules[0]…" (the #1512 note
catching it), and with that note also removed with exactly the reported
"Summary: 0 new, 1 modified, 0 deleted". Real app (11.13, git): the
issue's script shows the -/+ grant pair; mx check 0 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ako
ako merged commit a4aa675 into main Oct 27, 2026
33 checks passed
ako added a commit that referenced this pull request Oct 27, 2026
fix: the local-loop-silence and project-security clusters, and the diff-local fixes #1745 did not cover
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant