fix(refs): attribute/enum/workflow/mapping edges, deduped refs & impact, context related entities - #720
Merged
Conversation
The visitor stores DescribeFragmentFromStmt.ContainerType as "PAGE"/"SNIPPET"
while describeFragmentFrom switched on "page"/"snippet" with no default, so
neither branch ran and every widget was reported missing ("not found in page
M.P" — without even naming the widget). Same casing split as ALTER PAGE (#402)
and ALTER STYLING (#631).
Normalise with strings.ToLower (the convention of the other consumers), make
an unrecognised container type an error instead of an empty widget list, and
name the widget in the not-found message. The new tests parse the statement
and dispatch it through the registry, so they pin the visitor/executor casing
contract that a hand-built lowercase AST could not see.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`search` needs only a full catalog, which indexes string literals but not MDL source. On a project where `refresh catalog full source` had never run, the source half of every search came back empty with no word — read by agents as "no microflow/page mentions this". search now checks the build mode the catalog records (not the row count, so a built-but-empty index stays silent) and, below "source", prints a warning naming `refresh catalog full source` on a new ExecContext.Diagnostics writer (nil = stderr), keeping --format json stdout pure. The old unconditional "Tip: refresh catalog source" on stdout is replaced by it. Table format now delegates to execSearch up front instead of querying twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`show structure depth 1` (and its JSON form) filtered the catalog on MicroflowType = 'microflow' / 'nanoflow', while the catalog builder stores 'MICROFLOW' / 'NANOFLOW'. SQLite's `=` is case-sensitive, so both counts were always empty, and the summary omits zero counts, so every module appeared to have no flows at all. The builder's values are now exported constants (catalog.MicroflowTypeMicroflow/Nanoflow/Rule), used by the writer, by the structure query and by the linter's DocumentNoun switch, so reader and writer share one spelling. The test runs the real catalog builder over a MockBackend and reads the counts back through structureDepth1 / structureDepth1JSON, so it detects a casing drift between writer and reader; reverting only the reader to lower case makes it fail with the reported symptom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and mapped entities The refs graph stopped at documents. `impact Module.Entity.Attr` answered "not referenced" for an attribute a microflow writes and a page displays (Evora: DigitalTwin.Machine.NumberOfIncidents), an enumeration had no inbound edge at all, a workflow started only by a microflow had no caller, a page navigating an association and a mapping mapping an entity were invisible. - A raw-document walk over microflows, nanoflows, rules, pages, snippets, workflows and import/export mappings matches every string value against the names the model declares: whole-string matches are structured references (MemberChange.Attribute, AttributeRef.Attribute, EntityRefStep.Association, EnumerationType.Enumeration, ObjectMappingElement.Entity); inside expressions, association paths and qualified enumeration values. New kinds: member, type, value, mapping. - XPath constraints resolve bare attribute names against their target entity (and its generalizations), association paths, and enum attributes compared to a literal (kind xpath). Page/snippet constraints had no target entity because resolveEntityRefFromBSON read a key no stored EntityRef carries. - Entities -> enumerations from attribute types (kind type). - WorkflowCallAction -> WORKFLOW (kind call). - New types ATTRIBUTE, ENUMERATION, ENUMERATION_VALUE, IMPORT_MAPPING, EXPORT_MAPPING published in the lint-rule vocabulary; members kept off the graph_god_nodes asset side; CatalogSchemaVersion 15. Not covered: a bare member named through a variable in a free-text expression ($Order/Total), whose type is not known to the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y answer says what was checked `refs` and `impact` printed a row per edge, so a microflow with two retrieve activities over an entity appeared twice (Evora: ProductionLine_Reset on DigitalTwin.Machine); the impact summary counted those rows (MICROFLOW: 9 over six microflows) and printed the types in map order, different between runs. - select distinct, with a total order; the summary counts distinct elements per type, in type order, and the footer gives both numbers. - impact/refs on an enumeration include the edges to its values, with a Target column naming which value. - "(no impact - element is not referenced)" is gone. For an attribute or an enumeration value the message lists the sites that were checked and the ones that are not resolved (a member named through a variable in an expression; a decision branch on an enum), and says to run search first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e listed once `context DigitalTwin.Machine` said "Related Entities: (none found)" for an entity with five associations. The section read refs rows whose SOURCE is an entity, but an association edge's source is the ASSOCIATION, so only generalizations could ever match. It now reads both ends from the associations table (present in a fast catalog too), plus the generalization and the specializations. Also: Direct Callers / Shown By / workflow callers list each source once, and the enumeration context reads the same edge set as impact (type and value uses), grouped into entities, flows and pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ring it Studio Pro stores expressions exactly as typed, and a trailing newline left in the expression editor is common. describe interpolated the stored text verbatim, so `change $X (Status = Mod.Enum.Val` / `);` put the closing paren or semicolon on a line of its own (297 such lines in Evora Factory Management's microflows alone). One helper, describeExpr (TrimSpace; interior newlines kept), now renders every stored expression the describer emits: change/create members, set, change-list values, aggregate/reduce, list operations, call microflow / nanoflow / java / javascript / external action arguments, show page args, log node + template params, show message params, REST/web service/DB query params, while, decision and rule arguments, and page widget Visible/Editable conditions, action arguments, datasource arguments and client template parameters. It replaces the five ad-hoc TrimSuffix/TrimRight calls that each covered one slot. The stored model is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 tasks
… trimming expression whitespace Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ako
added a commit
that referenced
this pull request
Sep 27, 2026
…efix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptoms
Found by an agent-orientation audit on Evora Factory Management (MPR v1, Mendix 10.24):
impact DigitalTwin.Machine.NumberOfIncidentssaid(no impact - element is not referenced)although a change activity sets it andDigitalTwin.Machine_Detailsdisplays it.impact DigitalTwin.ENUM_MachineStatussaid the same.callers AltairIntegration.WF_ScheduleTechnicianAppointmentsaid(no callers found)althoughACT_TechnicianTicket_SumbitTriggerWFcalls it. An agent that trusts these answers deletes live elements.refs/impact DigitalTwin.MachinelistedFactoryManagement.ProductionLine_Reset | retrievetwice.MICROFLOW: 9over six microflows, and printed the types in map order, which changed between runs.context DigitalTwin.MachineshowedRelated Entities: (none found)for an entity with five associations.Causes
ATTRIBUTE,ENUMERATIONorENUMERATION_VALUEtargets.microflowActionRefhad no case forWorkflowCallAction. Nothing emitted page→association or mapping→entity edges. Page and snippet XPath constraints were also stored with an emptyTargetEntity(52 of 52 on Evora), becauseresolveEntityRefFromBSONreadEntityRef.QualifiedName, a key that no stored EntityRef has.DISTINCT. The summary counted rows by ranging over a map.ASSOCIATION, so the query could only ever find generalizations.Fix (one commit per concern)
fix(catalog)MemberChange.Attribute,AttributeRef.Attribute,EntityRefStep.Association,EnumerationType.Enumeration,ObjectMappingElement.Entity.resolveEntityRefFromBSONnow reads the stored shapes.type)WorkflowCallAction→WORKFLOW(call, socallerssees it)member,xpath,type,value,mapping.ATTRIBUTE,ENUMERATION,ENUMERATION_VALUE,IMPORT_MAPPING,EXPORT_MAPPING. They are published in the lint-rule vocabulary and the write-lint-rules skill.graphRefKinds, and attribute/value targets are kept offgraph_god_nodes' asset side. Attributes are members, not assets, so community and centrality results do not change.CatalogSchemaVersion15.CatalogTxgainsQuery, which*sql.Txalready has.fix(refs)select distinctwith a total order. The summary counts distinct elements per type, in a fixed order, and the footer gives both numbers.refs/impacton an enumeration include the uses of its values, with aTargetcolumn. The LIKE is escaped, becauseENUM_names contain_.search.fix(context)associationstable (both ends, with direction), plus the generalization and the specializations.impact.test(refs): an MDL repro and the findings.Not covered (said so in the empty-answer message)
$Order/Total,$dataView1/MachineStatus). Resolving it needs the variable's type.EnumerationCasestores the bare value name.Tests, and proof they detect the bugs
TestReferencesReachMembersEnumerationsWorkflowsAndMappings(mdl/catalog)buildXPathExpressions+buildReferencesover raw BSON fixtures shaped like Evora's unitsTestScanPaths%token%, predicate context, literal-in-string controlsTestMicroflowActionRef/WorkflowCallActionok = falseTestResolveEntityRefFromBSON(stored shapes)""for DirectEntityRef / IndirectEntityRef (re-checked by restoring the old function)TestShowReferencesListsEachEdgeOnce,TestShowImpactCountsElementsNotRows,TestShowImpactIsDeterministic,TestShowImpactOfEnumerationIncludesItsValues,TestShowImpactOfUnreferencedAttributeSaysWhatWasChecked(mdl/executor)TestAssembleEntityContextListsAssociatedEntities,TestAssembleMicroflowContextListsEachCallerOnce,TestAssembleEnumerationContextIncludesValueUses(none found)/ caller listed 3× / value uses missing (re-checked by restoring the oldcmd_context.go)Controls:
Documentationmust not produce an edge.mxcli linton Evora gives identical per-rule counts before and after: 231 issues each.Evora, before → after
impact DigitalTwin.Machine.NumberOfIncidentsimpact DigitalTwin.ENUM_MachineStatuscallers AltairIntegration.WF_ScheduleTechnicianAppointmentimpact DigitalTwin.Machinecontext DigitalTwin.Machine→ Related Entitiesimpacton an attribute with no edge (FactoryManagement.AgentExtension.DisplayName)Refs on Evora: 11,024 → 18,263. New: 4,043
member→ATTRIBUTE, 1,480member→ASSOCIATION, 558value, 356/306/67xpath(attribute/association/value), 284type, 144mapping, 1call→WORKFLOW.refresh catalog fullwent from 23s to 28s.The repro
mdl-examples/bug-tests/refs-graph-members-enums-workflows.mdlwas exec'd into a copy of Evora. Its expected outputs are in its header, and all of them reproduce with the old binary and are fixed with the new one.Validation
make build: passes.make lint: passes (Go and TypeScript).make test: 22 tests fail, all in widget/.mpk code this PR does not touch (TestParseMPK*,TestFindMPK*,TestDescribeWidget_*,TestValidateWidgetKind_*, …). They come from the sparse checkout omitting*.mpkblobs (open …/*.mpk: no such file, "0 enumeration properties learned from testdata .mpk files"), incmd/mxcli,mdl/executor,modelsdk/widgets/mpkandsdk/widgets/mpk. They are unrelated to this change.mx checkvalidation is needed: the catalog is never written back to the model.🤖 Generated with Claude Code