Skip to content

Context & catalog isolation — ExecContext gains Backend, handlers decoupled from mpr#229

Merged
ako merged 6 commits intomendixlabs:mainfrom
retran:feature/context-isolation
Apr 17, 2026
Merged

Context & catalog isolation — ExecContext gains Backend, handlers decoupled from mpr#229
ako merged 6 commits intomendixlabs:mainfrom
retran:feature/context-isolation

Conversation

@retran
Copy link
Copy Markdown
Contributor

@retran retran commented Apr 17, 2026

Summary

Third PR in the executor refactoring series (follows #222 typed errors, #224 dispatch registry, #225 handler migration).

  • ExecContext gains domain-specific fields: Backend, MprPath, SqlMgr, ThemeRegistry, Settings, Connected()/ConnectedForWrite() methods
  • Handler decoupling: ~550 e.reader/e.writer calls replaced with ctx.Backend across 66 files; e.quiet/e.format/e.fragments/e.mprPath/e.output replaced with ctx.* equivalents
  • Free function conversions: getEntityNames, getMicroflowNames, getPageNames converted from Executor methods to free functions using ctx.Backend/ctx.Cache; getThemeRegistryctx.GetThemeRegistry() lazy-load method
  • Catalog isolation: catalog.NewBuilder accepts new CatalogReader interface instead of concrete *mpr.Reader
  • Mock-based tests: showEnumerations handler tested with MockBackend, demonstrating the testability gains

~27 e := ctx.executor lines remain across ~18 files for legitimate reasons: Executor method calls (trackModifiedDomainModel, Execute, ExecuteProgram), shared state mutations (catalog, cache, sqlMgr), and concrete *mpr.Reader/*mpr.Writer type requirements (pageBuilder, lint, import/export mappings).

Commits (5)

# Description
1 ExecContext gains Backend/MprPath/SqlMgr/ThemeRegistry/Settings
2 Replace 154 reader/writer nil checks with Connected()/ConnectedForWrite()
3 Bulk reader/writer → Backend + e.field → ctx.Field (66 files, -264 lines net)
4 Convert name lookups and theme registry to free functions
5 CatalogReader interface + mock-based handler tests

Verification

  • make build — clean
  • make test — all pass
  • make lint — clean

Depends on: #225 (merged)

retran added 5 commits April 17, 2026 19:12
Demonstrate that handlers can now be tested without a real .mpr file
by using MockBackend with a pre-populated ContainerHierarchy cache.
Two tests verify table output and module filtering.
Copilot AI review requested due to automatic review settings April 17, 2026 18:07
@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

  • None found

Moderate Issues

  • None found

Minor Issues

  • None found

What Looks Good

  • Successful decoupling of executor handlers from direct mpr.Reader/mpr.Writer usage through ctx.Backend abstraction
  • Improved testability demonstrated by mock-based tests for showEnumerations handler
  • Consistent application of refactoring across 66 files with significant line reduction (-264 net)
  • Proper separation of concerns: ExecContext gains domain-specific fields, catalog isolation via CatalogReader interface
  • Free function conversions (getEntityNames, getMicroflowNames, etc.) improve modularity
  • All verification passes: clean build, all tests pass, clean lint
  • Each commit maintains single responsibility principle (backend fields, nil check replacement, bulk conversions, free functions, catalog interface)
  • Preserves existing error handling and functionality while improving code structure

Recommendation

Approve. This PR successfully refactors the executor layer to improve decoupling, testability, and maintainability without altering functionality, following the project's architectural guidelines. The changes are well-scoped, consistently applied, and verified.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the executor refactoring by making ExecContext the primary carrier of domain dependencies (via a Backend interface and related fields) and migrating handlers away from direct .mpr reader/writer usage, while also isolating catalog building behind an interface to reduce coupling and improve testability.

Changes:

  • Extend ExecContext with Backend, MprPath, SqlMgr, ThemeRegistry, Settings, and connection helper methods (Connected()/ConnectedForWrite()), then propagate these from Executor.
  • Migrate many handlers/utilities from e.reader/e.writer and e.* fields to ctx.Backend / ctx.* equivalents; convert some name lookups to free functions.
  • Introduce catalog isolation via a reader interface and add a mock-backend based handler test (showEnumerations).

Reviewed changes

Copilot reviewed 79 out of 79 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
mdl/executor/validate.go Switch validation connection checks to ctx.Connected() and remove executor backrefs.
mdl/executor/oql_type_inference.go Use ctx.Backend for entity/domain model lookup.
mdl/executor/hierarchy.go Gate hierarchy building on ctx.Connected() and use context cache.
mdl/executor/helpers.go Replace reader/writer calls with ctx.Backend and use ConnectedForWrite().
mdl/executor/format.go Keep executor output in sync during capture paths that still write via executor internals.
mdl/executor/executor_query.go Use ctx.Connected() for SHOW/DESCRIBE connection gating.
mdl/executor/executor_dispatch.go Populate new ExecContext fields from the executor (Backend, MprPath, etc.).
mdl/executor/executor_connect.go Populate e.backend via mprbackend.Wrap and use ConnectedForWrite() checks.
mdl/executor/executor.go Add backend.FullBackend to Executor and convert name lookup helpers to free functions using ctx.
mdl/executor/exec_context.go Add Backend/MprPath/SqlMgr/ThemeRegistry/Settings + helper methods.
mdl/executor/cmd_workflows_write.go Migrate workflow write operations to ctx.Backend.
mdl/executor/cmd_workflows.go Migrate workflow read paths to ctx.Backend.
mdl/executor/cmd_widgets.go Migrate widget raw unit read/write to ctx.Backend and connection checks to ctx.
mdl/executor/cmd_styling.go Use ctx.MprPath and ctx.Backend for styling operations.
mdl/executor/cmd_structure.go Replace reader-based counting with ctx.Backend calls.
mdl/executor/cmd_snippets.go Use ctx.Backend.ListSnippets().
mdl/executor/cmd_settings.go Use ctx.Backend for project settings read/write.
mdl/executor/cmd_security.go Use ctx.Backend for security reads and ctx.Format for format branching.
mdl/executor/cmd_rest_clients.go Use ctx.Backend for REST client read/write operations.
mdl/executor/cmd_rename.go Route rename/reference update operations through ctx.Backend.
mdl/executor/cmd_published_rest.go Use ctx.Backend for published REST operations and ctx.Quiet for messaging.
mdl/executor/cmd_pages_show.go Use ctx.Backend.ListPages().
mdl/executor/cmd_pages_describe_parse.go Use ctx.Backend for snippet/module resolution in parsing.
mdl/executor/cmd_pages_describe_output.go Use ctx.Backend for page ID → qualified name resolution.
mdl/executor/cmd_pages_describe.go Use ctx.Backend for pages/snippets/layouts/raw-unit reads.
mdl/executor/cmd_pages_create_v3.go Use ctx.Backend for page/snippet create/update/delete and ctx.GetThemeRegistry().
mdl/executor/cmd_pages_builder.go Use ctx.Backend for drop page/snippet operations.
mdl/executor/cmd_page_wireframe.go Use ctx.Backend for wireframe generation data access.
mdl/executor/cmd_odata.go Use ctx.Backend for OData and external entity operations.
mdl/executor/cmd_navigation.go Use ctx.Backend for navigation read/update.
mdl/executor/cmd_move.go Route move operations and reference updates through ctx.Backend.
mdl/executor/cmd_modules.go Migrate module operations and bulk deletes to ctx.Backend.
mdl/executor/cmd_module_overview.go Gate on ctx.Connected() for module overview generation.
mdl/executor/cmd_misc.go Use ctx.MprPath for UPDATE/REFRESH and ctx.Settings for SET.
mdl/executor/cmd_microflows_show.go Use ctx.Backend and new name lookup free functions for DESCRIBE MICROFLOW.
mdl/executor/cmd_microflows_format_action.go Use ctx.Connected()/ctx.Backend for legacy page ID lookup paths.
mdl/executor/cmd_microflows_drop.go Use ctx.Backend for microflow deletion and ctx.Cache for session tracking.
mdl/executor/cmd_microflows_create.go Use ctx.Backend for read/write paths during microflow creation.
mdl/executor/cmd_microflow_elk.go Use ctx.Backend for microflow ELK data access.
mdl/executor/cmd_mermaid.go Use ctx.Backend for mermaid generation data access.
mdl/executor/cmd_lint.go Use ctx.Connected() and ctx.MprPath for lint configuration and connectivity.
mdl/executor/cmd_layouts.go Use ctx.Backend.ListLayouts().
mdl/executor/cmd_jsonstructures.go Use ctx.Backend for JSON structure operations.
mdl/executor/cmd_javascript_actions.go Use ctx.Backend for JS action reads and ctx.MprPath for source lookup.
mdl/executor/cmd_javaactions.go Use ctx.Backend for Java action reads/writes and ctx.MprPath for source lookup.
mdl/executor/cmd_import_mappings.go Use ctx.Backend for import mapping operations.
mdl/executor/cmd_import.go Use ctx.Backend for settings/domain model reads in import flows.
mdl/executor/cmd_imagecollections.go Use ctx.Backend for image collection operations.
mdl/executor/cmd_fragments.go Use ctx.Backend to load page/snippet containers for fragment extraction.
mdl/executor/cmd_folders.go Use ctx.Backend for folder list/move/delete.
mdl/executor/cmd_features.go Use ctx.Connected() and ctx.Backend.ProjectVersion().
mdl/executor/cmd_export_mappings.go Use ctx.Backend for export mapping operations.
mdl/executor/cmd_enumerations_mock_test.go Add mock-backend based tests for showEnumerations.
mdl/executor/cmd_enumerations.go Use ctx.Backend for enumeration list/create/delete/describe.
mdl/executor/cmd_entities_describe.go Use ctx.Backend for entity listing and microflow lookup helpers.
mdl/executor/cmd_entities_access.go Use ctx.Backend when re-reading domain model after changes.
mdl/executor/cmd_entities.go Use ctx.Backend for entity/view-entity create/alter/drop flows and catalog querying via ctx.Catalog.
mdl/executor/cmd_domainmodel_elk.go Use ctx.Backend for ELK generation data access.
mdl/executor/cmd_diff_local.go Use ctx.Backend for version/contents directory and name lookups.
mdl/executor/cmd_diff.go Use ctx.Connected() gating and ctx.Backend for domain model/microflow comparisons.
mdl/executor/cmd_dbconnection.go Use ctx.Backend for database connection operations.
mdl/executor/cmd_datatransformer.go Use ctx.Backend for data transformer list/create/drop and ctx.Quiet messaging.
mdl/executor/cmd_contract.go Use ctx.Backend for contract-related model access and writes.
mdl/executor/cmd_constants.go Use ctx.Backend for constants and project settings access.
mdl/executor/cmd_catalog.go Use ctx.MprPath and ctx.Backend for catalog caching and pre-warm operations.
mdl/executor/cmd_businessevents.go Use ctx.Backend for business event service operations.
mdl/executor/cmd_associations.go Use ctx.Backend for association create/alter/drop/show/describe paths.
mdl/executor/cmd_alter_workflow.go Use ctx.Backend for raw workflow BSON read/write.
mdl/executor/cmd_alter_page.go Use ctx.Backend for raw page/snippet BSON read/write and ctx.GetThemeRegistry().
mdl/executor/cmd_agenteditor_models.go Use ctx.Backend for agent editor model listing/lookup.
mdl/executor/cmd_agenteditor_mcpservices.go Use ctx.Backend for agent editor MCP service listing/lookup.
mdl/executor/cmd_agenteditor_kbs.go Use ctx.Backend for agent editor KB listing/lookup.
mdl/executor/cmd_agenteditor_agents.go Use ctx.Backend for agent editor agent listing/lookup.
mdl/executor/autocomplete.go Use ctx.Connected() and ctx.Backend across autocomplete sources.
mdl/backend/mpr/backend.go Add Wrap(*mpr.Writer, path) helper and expose ListJavaActionsFull().
mdl/backend/mock/mock_java.go Add mock implementation hook for ListJavaActionsFull().
mdl/backend/mock/backend.go Add ListJavaActionsFullFunc field to MockBackend.
mdl/backend/java.go Extend Java backend interface with ListJavaActionsFull().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/cmd_javaactions.go
@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

  • Systematic replacement of direct mpr.Reader/mpr.Writer accesses with ctx.Backend interface calls across 66 files
  • Centralized connection state checking through new Connected()/ConnectedForWrite() methods on ExecContext
  • Improved testability via CatalogReader interface and mock-based handler tests demonstrating the refactor's benefits
  • Consistent application of changes: e.reader/e.writerctx.Backend, e.quiet/e.format/e.fragments/e.mprPath/e.outputctx.* equivalents
  • Each commit has a single, clear focus following good atomicity principles:
    1. ExecContext gains domain-specific fields
    2. Replace reader/writer nil checks with connection methods
    3. Bulk conversions of field accesses
    4. Free function conversions for name lookups
    5. CatalogReader interface + mock-based tests
  • Free function conversions (getEntityNames, getMicroflowNames, etc.) improve modularity by extracting logic from Executor
  • Lazy-loading theme registry via ctx.GetThemeRegistry() replaces direct method calls
  • Removal of unused variable suppressions (_ = e) cleans up dead code
  • All verification passes: make build clean, make test all pass, make lint clean
  • Mock-based tests validate improved testability of the refactored design

Recommendation

Approve. This is a well-executed refactor that successfully decouples the executor from direct mpr package usage while preserving behavior. The changes are mechanical and systematic, reducing introduction of errors. The refactor improves code structure, testability, and maintainability without altering external functionality. All verification checks pass, confirming correctness.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

This PR represents a well-executed refactoring effort that successfully decouples handlers from direct executor dependencies while improving testability. The changes are mechanical and consistent across the codebase:

  • Clean interface introduction: The Backend interface and CatalogReader interface properly abstract away concrete implementations
  • Consistent pattern replacement: Nearly all e.reader/e.writer accesses are replaced with ctx.Backend equivalents, and executor fields are replaced with context fields
  • Improved testability: The mock-based tests demonstrate the refactoring's goal of making handlers more testable
  • Legitimate exceptions preserved: The ~27 remaining e := ctx.executor lines are appropriately kept for cases requiring direct executor access (method calls, state mutations, concrete type needs)
  • Commit organization: Each commit addresses a specific aspect of the refactoring (context enhancement, nil check replacement, bulk conversion, free function extraction, catalog isolation)

The refactoring maintains backward compatibility while setting up better abstractions for future changes. Error handling patterns are preserved through the Connected()/ConnectedForWrite() methods, and the catalog isolation work correctly introduces the CatalogReader interface.

Recommendation

Approve this PR. It successfully accomplishes its stated goals of executor refactoring and catalog isolation without introducing functional changes, improves testability through interface-based design, and follows the project's architectural direction. The changes are mechanical, consistent, and well-tested.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/executor_connect.go
Comment thread mdl/executor/exec_context.go
Comment thread mdl/executor/cmd_catalog.go
Comment thread mdl/executor/cmd_misc.go
@retran retran force-pushed the feature/context-isolation branch from 9730b80 to 790f63b Compare April 17, 2026 18:52
@retran retran requested a review from Copilot April 17, 2026 18:53
@github-actions
Copy link
Copy Markdown

AI Code Review

Let me review this PR against the checklist. This PR is part of an executor refactoring series that aims to decouple handlers from direct executor fields and use the ExecContext instead.

First, I'll look for any critical issues:

Critical Issues

Looking through the diff, I don't see any obvious critical bugs or correctness issues. The changes are primarily replacing direct executor field accesses (e.g., e.reader, e.writer) with corresponding methods on the ExecContext (e.g., ctx.Backend, ctx.Connected(), etc.).

Let me check for any specific issues:

  1. In cmd_alter_workflow.go, I see a change in applyDropBoundaryEvent:

    -func applyDropBoundaryEvent(doc bson.D, op *ast.DropBoundaryEventOp) error {
    +func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {

    And then:

    -	if len(events) > 1 {
    -		fmt.Printf("warning: activity %q has %d boundary events; dropping the first one\n", op.ActivityRef, len(events))
    +	if len(events) > 1 {
    +		fmt.Fprintf(w, "warning: activity %q has %d boundary events; dropping the first one\n", op.ActivityRef, len(events))

    This looks correct - they're now passing an io.Writer to the function and using fmt.Fprintf instead of fmt.Printf. The test file also correctly updates to use io.Discard.

  2. I notice that in several places, they're replacing e.reader == nil checks with !ctx.Connected(). This seems correct based on the commit description that says they added Connected()/ConnectedForWrite() methods to ExecContext.

  3. They're replacing direct accesses to executor fields like:

    • e.readerctx.Backend
    • e.writerctx.Backend (since Backend interface likely includes both reader and writer capabilities)
    • e.quietctx.Quiet
    • e.formatctx.Format
    • e.fragmentsctx.Fragments
    • e.mprPathctx.MprPath
    • e.outputctx.Output
    • e.cachectx.Cache
    • e.getThemeRegistry()ctx.GetThemeRegistry()

This is consistent with the PR description.

Let me check for any potential issues:

In cmd_catalog.go, I see:

-	builder := catalog.NewBuilder(cat, e.reader)
+	builder := catalog.NewBuilder(cat, ctx.Backend)

And in catalog/builder.go:

-func NewBuilder(catalog *Catalog, reader *mpr.Reader) *Builder {
+func NewBuilder(catalog *Catalog, reader CatalogReader) *Builder {

This is good - they're introducing a CatalogReader interface to decouple the catalog builder from the concrete *mpr.Reader type, which aligns with the PR description.

Let me check if there are any places where the refactoring might be incomplete:

Looking through the diff, I see many consistent replacements. The PR mentions that ~27 e := ctx.executor lines remain across ~18 files for legitimate reasons, which seems reasonable.

Let me check for any MDL syntax changes - I don't see any changes to MDL grammar, AST, or executor handlers that would introduce new MDL syntax. This appears to be purely a refactoring of the executor implementation.

Now let me check the checklist items:

Overlap & duplication

  • The PR doesn't appear to introduce duplicate functionality - it's refactoring existing code
  • No new MDL syntax is being added, so no overlap with existing proposals or doctype tests in terms of functionality
  • The changes are consistent with the executor refactoring series mentioned in the summary

MDL syntax design

  • No new or modified MDL syntax is introduced in this diff
  • All changes are internal refactorings

Full-stack consistency

  • Not applicable since no new MDL features are being added

Test coverage

  • The PR mentions mock-based tests for showEnumerations handler
  • The diff shows test updates (like in cmd_alter_workflow_test.go)
  • No new packages are introduced that would need test files

Security & robustness

  • No obvious security issues spotted
  • The changes replace direct field accesses with method calls, which could potentially improve encapsulation
  • No file I/O in hot paths introduced
  • No silent side effects on typos apparent

Scope & atomicity

  • The PR is scoped to a single concern: decoupling handlers from executor fields and using ExecContext
  • Each commit appears to do one thing (as shown in the commit table)
  • The PR description focuses on the executor refactoring series

Code quality

  • The refactoring appears to be applied consistently across many files
  • No obviously missed conversions of the pattern being replaced
  • The introduction of interfaces (like CatalogReader) improves design

Let me double-check a few specific areas:

In cmd_alter_workflow.go, the applyDropBoundaryEvent function signature change looks correct and is consistently updated in its callers and tests.

In cmd_catalog.go, the removal of _ = e // suppress unused comments is appropriate since they're no longer using the executor variable.

In cmd_constants.go and similar files, the pattern of replacing:

-	e := ctx.executor
-
-	constants, err := e.reader.ListConstants()
+	constants, err := ctx.Backend.ListConstants()

is consistently applied and correct.

One thing I should check: in the preWarmCache function in cmd_catalog.go, I see:

-func preWarmCache(ctx *ExecContext) {
-	e := ctx.executor
-	h, _ := getHierarchy(ctx)
-	if h == nil || ctx.Cache == nil {
- 		return
- 	}
-
-	// Build entity name lookup
-	ctx.Cache.entityNames = make(map[model.ID]string)
-	dms, _ := e.reader.ListDomainModels()
+func preWarmCache(ctx *ExecContext) {
+	h, _ := getHierarchy(ctx)
+	if h == nil || ctx.Cache == nil {
+ 		return
+ 	}
+
+	// Build entity name lookup
+	ctx.Cache.entityNames = make(map[model.ID]string)
+	dms, _ := ctx.Backend.ListDomainModels()

This looks correct - they removed the unnecessary e := ctx.executor line and replaced e.reader with ctx.Backend.

Let me check if there are any remaining instances of the old pattern that should have been converted:

Scanning through the diff, I don't see any obvious missed conversions. The PR author mentioned that ~27 lines remain for legitimate reasons, which seems plausible for cases where they need direct access to the executor for specific methods like trackModifiedDomainModel, Execute, ExecuteProgram, or when they need concrete types like *mpr.Reader/*mpr.Writer.

Let me check if the changes maintain the same behavior:

The key change is replacing e.reader == nil with !ctx.Connected(). I need to verify that ctx.Connected() correctly represents whether the reader is available.

From the PR description: "ExecContext gains domain-specific fields: Backend, MprPath, SqlMgr, ThemeRegistry, Settings, Connected()/ConnectedForWrite() methods"

And: "Replace 154 reader/writer nil checks with Connected()/ConnectedForWrite()"

This suggests that Connected() returns true when the backend (and thus the reader) is available, which makes sense.

Similarly, ConnectedForWrite() would check if the writer is available.

This is a good improvement as it encapsulates the connection state checking in the ExecContext rather than having each handler check for nil reader/writer.

Let me look for any potential issues with the MockBackend:

In mock/backend.go, they added:

	ListJavaActionsFullFunc        func() ([]*javaactions.JavaAction, error)

And in mock/mock_java.go:

func (m *MockBackend) ListJavaActionsFull() ([]*javaactions.JavaAction, error) {
	if m.ListJavaActionsFullFunc != nil {
		return m.ListJavaActionsFullFunc()
	}
	return nil, nil
}

This follows the same pattern as the other mock methods and looks correct.

In mpr/backend.go, they added:

func (b *MprBackend) ListJavaActionsFull() ([]*javaactions.JavaAction, error) {
	return b.reader.ListJavaActionsFull()
}

Assuming the mpr.Reader has a ListJavaActionsFull() method (which it should, based on the JavaBackend interface changes), this is correct.

Let me check if there are any missing interface updates:

The JavaBackend interface in mdl/backend/java.go gained a ListJavaActionsFull() method:

	ListJavaActionsFull() ([]*javaactions.JavaAction, error)

This is implemented in both the mock backend and the MPR backend, so it should be fine.

Now, let me check if there are any places where they might have incorrectly replaced something:

In cmd_diff.go, I see:

-			oldOutput := e.output
-			e.output = &buf
+			oldOutput := ctx.Output
+			ctx.Output = &buf

This looks correct - they're saving and restoring the output buffer using the context.

In cmd_diff_local.go:

-	if e.reader.Version() != 2 {
+	if ctx.Backend.Version() != 2 {

Assuming the Backend interface has a Version() method, this is correct.

Let me check if the Backend interface actually has these methods:

From the PR description, ExecContext gained a Backend field, and they're replacing e.reader/e.writer calls with ctx.Backend.

Looking at the changes, it seems the Backend interface now includes methods like:

  • ListModules()
  • GetDomainModel()
  • CreateAssociation()
  • etc.

And also methods like:

  • Version()
  • ContentsDir()
  • GetMendixVersion()
  • GetRawUnitBytes()
  • UpdateRawUnit()
  • etc.

This makes sense as the Backend is meant to encapsulate the data access layer.

One thing I should verify: in several places, they're using ctx.Backend where previously they used e.reader for read operations and e.writer for write operations. This implies that the Backend interface combines both reader and writer capabilities, which is reasonable for an interface that provides access to the MPR backend.

Let me check if there are any inconsistencies:

In cmd_alter_page.go:

-	if e.reader == nil {
+	if !ctx.Connected() {
 		return mdlerrors.NewNotConnected()
 	}
-	if e.writer == nil {
+	if !ctx.ConnectedForWrite() {
 		return mdlerrors.NewNotConnectedWrite()
 	}

This correctly separates the read and write connection checks.

In cmd_alter_page.go, later:

-	rawBytes, err := e.reader.GetRawUnitBytes(unitID)
+	rawBytes, err := ctx.Backend.GetRawUnitBytes(unitID)

And:

-	if err := e.writer.UpdateRawUnit(string(unitID), outBytes); err != nil {
+	if err := ctx.Backend.UpdateRawUnit(string(unitID), outBytes); err != nil {

This suggests that the Backend interface has both GetRawUnitBytes (reader) and UpdateRawUnit (writer) methods, which makes sense.

Let me check if there are any places where they might have missed converting something:

Scanning through the diff, I see many consistent patterns. For example, in cmd_agenteditor_agents.go:

-	e := ctx.executor
-	if e.reader == nil {
+	if !ctx.Connected() {
 		return mdlerrors.NewNotConnected()
 	}
 
-	agents, err := e.reader.ListAgentEditorAgents()
+	agents, err := ctx.Backend.ListAgentEditorAgents()

This is correct.

I think the refactoring is well-executed and consistent.

Let me check if there are any issues with the test changes:

In cmd_alter_workflow_test.go, they changed:

-	if err := applyDropBoundaryEvent(doc, op); err != nil {
+	if err := applyDropBoundaryEvent(io.Discard, doc, op); err != nil {

This is correct given the function signature change.

Now, let me consider if there are any broader architectural concerns:

The PR is part of a series refactoring the executor to improve testability and decouple concerns. This specific PR:

  1. Makes ExecContext richer with domain-specific fields
  2. Decouples handlers from direct executor field access
  3. Converts some executor methods to free functions
  4. Introduces interfaces for better abstraction (CatalogReader)
  5. Adds mock-based tests to demonstrate improved testability

This aligns well with the project's architecture goals mentioned in CLAUDE.md.

Let me check if there are any violations of the MDL syntax design guidelines - since no MDL syntax is being added or modified, there's nothing to check here.

For full-stack consistency: not applicable since no new MDL features.

Test coverage: The PR mentions mock-based tests for showEnumerations handler, and I see test updates in the diff, so this seems addressed.

Security & robustness: The changes improve encapsulation by reducing direct access to executor internals, which is a positive for robustness.

Scope & atomicity: The PR is focused on executor refactoring, which is a single concern.

Code quality: The refactoring appears consistent and complete.

Let me do one final check for any obvious mistakes:

In cmd_catalog.go, in the getCachePath function:

-func getCachePath(ctx *ExecContext) string {
-	e := ctx.executor
-	if e.mprPath == "" {
+	if ctx.MprPath == "" {
 		return ""
 	}
-	dir := filepath.Dir(e.mprPath)
+	dir := filepath.Dir(ctx.MprPath)

This looks correct - they removed the unnecessary executor variable and used ctx.MprPath directly.

In getMprModTime:

-func getMprModTime(ctx *ExecContext) time.Time {
-	e := ctx.executor
-	if e.mprPath == "" {
+	if ctx.MprPath == "" {
 		return time.Time{}
 	}
-	info, err := os.Stat(e.mprPath)
+	info, err := os.Stat(ctx.MprPath)

Also correct.

In isCacheValid:

-func isCacheValid(ctx *ExecContext, cachePath string, requiredMode string) (bool, string) {
-	e := ctx.executor
 	// Check if cache file exists
 	if _, err := os.Stat(cachePath); os.IsNotExist(err) {
 		return false, "cache file does not exist"
 	}
 
 	// Check MPR path matches
-	if info.MprPath != e.mprPath {
+	if info.MprPath != ctx.MprPath {

Correct.

In buildCatalog:

-	version, _ := e.reader.GetMendixVersion()
+	version, _ := ctx.Backend.GetMendixVersion()

And:

-	builder := catalog.NewBuilder(cat, e.reader)
+	builder := catalog.NewBuilder(cat, ctx.Backend)

Correct.

-cat.SetCacheInfo(e.mprPath, getMprModTime(ctx), version, buildMode, elapsed)
+cat.SetCacheInfo(ctx.MprPath, getMprModTime(ctx), version, buildMode, elapsed)

Correct.

In execRefreshCatalogStmt:

-func execRefreshCatalogStmt(ctx *ExecContext, stmt *ast.RefreshCatalogStmt) error {
-	e := ctx.executor
-	if e.reader == nil {
+	if !ctx.Connected() {
 		return mdlerrors.NewNotConnected()
 	}

Correct.

In captureDescribeParallel:

-	// Create a goroutine-local executor: shared reader + cache, own output buffer.
-	// TODO: Replace with ExecContext.Fork() when MR 3 makes ExecContext self-contained.
+	// Create a goroutine-local context: shared backend + cache, own output buffer.
 	var buf bytes.Buffer
 	local := &Executor{
 		reader: ctx.executor.reader,
@@ -700,8 +692,14 @@ func captureDescribeParallel(ctx *ExecContext, objectType string, qualifiedName
 		cache:  ctx.Cache,
 	}
 	localCtx := &ExecContext{
+		Context:  ctx.Context,
 		Output:   &buf,
+		Format:   ctx.Format,
+		Quiet:    ctx.Quiet,
+		Logger:   ctx.Logger,
+		Backend:  ctx.Backend,
 		Cache:    ctx.Cache,
+		MprPath:  ctx.MprPath,
 		executor: local,
 	}

This is interesting - they're creating a local Executor but setting its reader to ctx.executor.reader rather than going through the context's backend. However, the comment says they plan to replace this with ExecContext.Fork() when MR 3 makes ExecContext self-contained, so this seems like a temporary measure that's consistent with their ongoing refactoring.

The local ExecContext is properly initialized with:

  • Context: ctx.Context
  • Output: &buf
  • Format: ctx.Format
  • Quiet: ctx.Quiet
  • Logger: ctx.Logger
  • Backend: ctx.Backend
  • Cache: ctx.Cache
  • MprPath: ctx.MprPath
  • executor: local

This looks correct for their intermediate state.

In preWarmCache:

-func preWarmCache(ctx *ExecContext) {
-	e := ctx.executor
 	h, _ := getHierarchy(ctx)
 	if h == nil || ctx.Cache == nil {
 		return
 	}
 
 	// Build entity name lookup
 	ctx.Cache.entityNames = make(map[model.ID]string)
-	dms,

---
*Automated review via OpenRouter (Nemotron Super 120B) — [workflow source](https://github.com/mendixlabs/mxcli/blob/main/.github/workflows/ai-review.yml)*

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/cmd_misc.go
@retran retran force-pushed the feature/context-isolation branch from 790f63b to b46f0bf Compare April 17, 2026 18:58
@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • In mdl/executor/cmd_alter_workflow.go, the applyDropBoundaryEvent function signature changed to include an io.Writer parameter, but the corresponding test file updates only show partial changes (the diff shows test updates but the function definition change might need verification in other call sites). However, since the PR states all tests pass and this appears to be an intentional refactor to pass the writer through, this is likely correct.

What Looks Good

  • Excellent architectural refactoring that successfully decouples handlers from concrete mpr implementations
  • Comprehensive application of the ExecContext pattern across 66 files with consistent replacement of e.reader/e.writer with ctx.Backend
  • Thoughtful migration path provided via the Wrap function in mpr/backend.go
  • Clean interface design with CatalogReader enabling true isolation of catalog builder from mpr specifics
  • Proper separation of concerns: read operations use ctx.Backend, write operations properly checked via ctx.ConnectedForWrite()
  • Mock-based tests demonstrate improved testability as intended
  • All verification steps pass (make build, test, lint)
  • The refactor maintains all existing functionality while improving code structure and testability

Recommendation

Approve. This PR successfully executes the executor refactoring goals outlined in the summary, improving testability and decoupling through proper interface usage while maintaining full backward compatibility. The changes are mechanical but significant, applied consistently across the codebase, and verified to work correctly. No syntax or behavioral changes to MDL features were made, so the full-stack consistency checklist doesn't apply. The refactor achieves its intended architectural improvements without introducing regressions.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/executor.go
Comment thread mdl/executor/executor.go
Comment thread mdl/executor/executor.go
Comment thread mdl/executor/exec_context.go
@retran retran force-pushed the feature/context-isolation branch from b46f0bf to 974d94c Compare April 17, 2026 19:07
@github-actions
Copy link
Copy Markdown

AI Code Review

Okay, let me review this pull request. The user has provided a detailed PR summary and the diff, and I need to review it against the checklist in CLAUDE.md.

First, I'll look at the PR summary to understand what changes are being made:

This is the third PR in the executor refactoring series. Key changes:

  • ExecContext gains domain-specific fields: Backend, MprPath, SqlMgr, ThemeRegistry, Settings, Connected()/ConnectedForWrite() methods
  • Handler decoupling: ~550 e.reader/e.writer calls replaced with ctx.Backend across 66 files; e.quiet/e.format/e.fragments/e.mprPath/e.output replaced with ctx.* equivalents
  • Free function conversions: getEntityNames, getMicroflowNames, getPageNames converted from Executor methods to free functions using ctx.Backend/ctx.Cache; getThemeRegistry → ctx.GetThemeRegistry() lazy-load method
  • Catalog isolation: catalog.NewBuilder accepts new CatalogReader interface instead of concrete *mpr.Reader
  • Mock-based tests: showEnumerations handler tested with MockBackend

Now, I need to check the diff against the review checklist. The checklist has several sections: Overlap & duplication, MDL syntax design, Full-stack consistency, Test coverage, Security & robustness, Scope & atomicity, Code quality, Bugs & correctness.

Since this is a refactoring PR (not adding new MDL syntax), I should focus on whether the refactoring is done correctly according to the architecture guidelines.

Let me scan through the diff to see what's changed:

  1. mdl/backend/java.go: Added ListJavaActionsFull() method to JavaBackend interface
  2. mdl/backend/mock/backend.go: Added ListJavaActionsFullFunc to MockBackend
  3. mdl/backend/mock/mock_java.go: Implemented ListJavaActionsFull for MockBackend
  4. mdl/backend/mpr/backend.go: Added Wrap function and ListJavaActionsFull implementation
  5. mdl/catalog/builder.go: Introduced CatalogReader interface and changed NewBuilder to accept CatalogReader instead of *mpr.Reader
  6. Many files in mdl/executor/: Replaced e.reader/e.writer with ctx.Backend, e.quiet/e.format etc. with ctx.* equivalents, and changed executor method calls to free functions

Let me check against the checklist:

Overlap & duplication

  • No new proposals mentioned in the PR
  • The changes seem to be refactoring existing code, not duplicating functionality
  • No doctype test files mentioned as being affected (the PR doesn't add new tests, but mentions mock-based tests for showEnumerations)
  • The PR doesn't re-document already-shipped features as new - it's a refactor

MDL syntax design

  • This PR doesn't add or modify any MDL syntax (no changes to grammar, AST, etc.)
  • So this section doesn't apply

Full-stack consistency

  • Since no new MDL syntax is added, this section doesn't apply
  • The PR is about refactoring the executor/backend, not adding new MDL features

Test coverage

  • The PR mentions: "Mock-based tests: showEnumerations handler tested with MockBackend"
  • I should check if there are test files added/modified
  • Looking at the diff, I don't see any test files changed in the visible portion (the diff was truncated)
  • But the PR summary says: "Mock-based tests: showEnumerations handler tested with MockBackend, demonstrating the testability gains"
  • Since the diff was truncated, I need to assume there are test changes not visible in the provided diff
  • However, the PR says "make test — all pass" so tests are passing

Security & robustness

  • No obvious security issues in the refactoring
  • The changes are about decoupling and using interfaces, which should improve testability and robustness
  • No Unix sockets, file I/O in hot paths, or silent side effects apparent from the diff

Scope & atomicity

  • The PR is described as "Third PR in the executor refactoring series" with a clear focus: decoupling handlers from mpr by introducing ExecContext with Backend
  • Each commit seems to do one thing:
    1. ExecContext gains Backend/MprPath/SqlMgr/ThemeRegistry/Settings
    2. Replace reader/writer nil checks with Connected()/ConnectedForWrite()
    3. Bulk reader/writer → Backend + e.field → ctx.Field
    4. Convert name lookups and theme registry to free functions
    5. CatalogReader interface + mock-based handler tests
  • This follows the "each commit does one thing" principle
  • The PR is scoped to a single concern: executor refactoring for decoupling

Code quality

  • The refactoring appears consistent: replacing e.* with ctx.* across many files
  • Introducing interfaces (CatalogReader, Backend) for better abstraction
  • No manually maintained lists apparent in the diff
  • Design docs should match implementation - since this is refactoring existing functionality, it should align with the architecture described in CLAUDE.md

Bugs & correctness

  • The changes seem logical: replacing direct reader/writer calls with Backend interface methods
  • The Connected()/ConnectedForWrite() methods replace nil checks, which is a good pattern
  • The free function conversions make sense for testability
  • CatalogReader interface improves isolation

Now, let me check for any potential issues:

  1. In mdl/executor/cmd_alter_workflow.go, I see a change in applyDropBoundaryEvent:

    • Before: func applyDropBoundaryEvent(doc bson.D, op *ast.DropBoundaryEventOp) error
    • After: func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error
      And in the call site: applyDropBoundaryEvent(ctx.Output, rawData, o)
      This seems correct - they're passing the output writer to print warnings.
  2. In mdl/executor/cmd_alter_workflow_test.go, they updated the tests to use io.Discard:

    • Before: applyDropBoundaryEvent(doc, op)
    • After: applyDropBoundaryEvent(io.Discard, doc, op)
      This matches the function signature change.
  3. In mdl/catalog/builder.go, they introduced CatalogReader interface. Let me verify it's correctly used:

    • NewBuilder now takes CatalogReader instead of *mpr.Reader
    • In cmd_catalog.go, they changed builder := catalog.NewBuilder(cat, e.reader) to builder := catalog.NewBuilder(cat, ctx.Backend)
    • This assumes ctx.Backend implements CatalogReader, which it should based on the changes in mpr/backend.go and the interface definition.
  4. Checking if Backend interface has all the methods needed:

    • From the diff, I see ListJavaActionsFull was added to JavaBackend, and implemented in MprBackend
    • The CatalogReader interface in catalog/builder.go lists many methods like ListModules, GetProjectSettings, etc.
    • I need to verify that the Backend interface (which ctx.Backend implements) has all these methods.

Looking at the backend definitions:

  • In mdl/backend/mpr/backend.go, MprBackend has methods like ListModules, GetProjectSettings, etc.
  • The JavaBackend extension was added separately.

The PR summary says: "ExecContext gains domain-specific fields: Backend, MprPath, SqlMgr, ThemeRegistry, Settings"

And in the diff, I see in various files:

  • ctx.Backend.ListModules()
  • ctx.Backend.GetProjectSettings()
  • etc.

So it seems the Backend interface is meant to encapsulate all the backend operations.

Let me check if there are any missing method implementations that would cause compilation errors.

Since the PR says "make build — clean", I'll assume it compiles.

One thing I noticed: in mdl/executor/cmd_catalog.go, there's a comment:

// Create a goroutine-local executor: shared reader + cache, own output buffer.
// TODO: Replace with ExecContext.Fork() when MR 3 makes ExecContext self-contained.

And they changed it to:

// Create a goroutine-local context: shared backend + cache, own output buffer.

This shows they're in the process of making ExecContext self-contained, which aligns with the PR goal.

Now, for the checklist items that don't apply (MDL syntax design, Full-stack consistency for MDL features), I should mark them as not applicable since this PR doesn't touch MDL syntax.

Let me structure my review:

Critical Issues: None found
Moderate Issues: None found
Minor Issues: Maybe one or two small things
What Looks Good: The refactoring is well-scoped, consistent, and improves testability
Recommendation: Approve

Let me look for any minor issues:

  1. In mdl/executor/cmd_alter_workflow.go, the applyDropBoundaryEvent function now takes an io.Writer parameter, but in the function body, they use:
    if len(events) > 1 {
    
  •   fmt.Printf("warning: activity %q has %d boundary events; dropping the first one\n", op.ActivityRef, len(events))
    
  •   fmt.Fprintf(w, "warning: activity %q has %d boundary events; dropping the first one\n", op.ActivityRef, len(events))
    
    }
    This is correct - they're using the passed writer.
    
    
  1. In the same file, there's a call:
    case *ast.DropBoundaryEventOp:
    
  •   	if err := applyDropBoundaryEvent(e, rawData, o); err != nil {
    
  •   	if err := applyDropBoundaryEvent(ctx, rawData, o); err != nil {
    
    But the function signature is now applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error
    So they're passing ctx as the first argument, but ctx is *ExecContext, not io.Writer.
    
    Wait, let me check again:
    
    The diff shows:
    
    case *ast.DropBoundaryEventOp:
  •   	if err := applyDropBoundaryEvent(e, rawData, o); err != nil {
    
  •   	if err := applyDropBoundaryEvent(ctx, rawData, o); err != nil {
    
    
    And the function is:
    
    func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {
    
    So they're passing ctx (which is *ExecContext) as the first argument, but it should be io.Writer.
    
    This looks like a bug! ctx is not an io.Writer.
    
    Let me verify by looking at the context:
    
    In execAlterWorkflow, ctx is *ExecContext.
    
    And ExecContext has an Output field which is an io.Writer (from the diff in other files, e.g., in cmd_alter_page.go: ctx.Output).
    
    So it should be ctx.Output, not ctx.
    
    Let me check if this is consistent elsewhere.
    
    In the captureDescribeParallel function in cmd_catalog.go, I see:
    
    var buf bytes.Buffer
    local := &Executor{
    reader: ctx.executor.reader,
    writer: nil,
    output: &buf,
    }
    
    And in the ExecContext definition (not shown in diff, but implied), Output is likely an io.Writer.
    
    Looking at the changes in cmd_alter_page.go:
    
    • if err := e.writer.UpdateRawUnit(string(unitID), outBytes); err != nil {
    • if err := ctx.Backend.UpdateRawUnit(string(unitID), outBytes); err != nil {
    
    But for output, in cmd_diff.go:
    
    •    	oldOutput := e.output
      
    •    	e.output = &buf
      
    •    	oldOutput := ctx.Output
      
    •    	ctx.Output = &buf
      
    
    So ctx.Output is indeed an io.Writer.
    
    Therefore, in cmd_alter_workflow.go, it should be:
    
    applyDropBoundaryEvent(ctx.Output, rawData, o)
    
    But the diff shows:
    
    applyDropBoundaryEvent(ctx, rawData, o)
    
    This is incorrect.
    
    Let me double-check the diff snippet:
    
    From the diff:
    
      case *ast.DropBoundaryEventOp:
    
  •   	if err := applyDropBoundaryEvent(e, rawData, o); err != nil {
    
  •   	if err := applyDropBoundaryEvent(ctx, rawData, o); err != nil {
    
    
    And the function definition:
    
    func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {
    
    Yes, this is a bug. They meant to pass ctx.Output but passed ctx.
    
    However, looking further down in the same file, in the test changes:
    
    In cmd_alter_workflow_test.go:
    
    • if err := applyDropBoundaryEvent(doc, op); err != nil {
    • if err := applyDropBoundaryEvent(io.Discard, doc, op); err != nil {
    
    So in the test, they're correctly passing io.Discard (which is io.Writer) as the first argument.
    
    But in the production code, they're passing ctx instead of ctx.Output.
    
    This is likely a mistake in the diff.
    
    Let me see if there are other instances.
    
    Actually, looking at the function definition change:
    
    -func applyDropBoundaryEvent(doc bson.D, op *ast.DropBoundaryEventOp) error {
    +func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {
    
    And then in the function body, they use w for the warning:
    
    •    fmt.Fprintf(w, "warning: activity %q has %d boundary events; dropping the first one\n", op.ActivityRef, len(events))
      
    
    So w must be an io.Writer.
    
    Therefore, the call site should pass an io.Writer, not *ExecContext.
    
    Given that in the test they use io.Discard, and in other places ctx.Output is used for output, it should be ctx.Output.
    
    This is a moderate issue because it would cause a compilation error (cannot use ctx (type *ExecContext) as type io.Writer in argument to applyDropBoundaryEvent).
    
    Since the PR says "make build — clean", either:
    - This part of the diff is incorrect in what I'm seeing (maybe the diff was truncated and I'm misreading)
    - Or there's more context I'm missing
    - Or my assumption about ctx.Output is wrong
    
    Let me look for other uses of ctx.Output in the diff to confirm.
    
    In cmd_diff.go:
    
    •    	oldOutput := e.output
      
    •    	e.output = &buf
      
    •    	oldOutput := ctx.Output
      
    •    	ctx.Output = &buf
      
    
    In cmd_diff_local.go, I don't see Output usage in the visible diff.
    
    In cmd_alter_page.go, I don't see Output usage in the visible diff.
    
    But in the PR description, it says: "e.quiet/e.format/e.fragments/e.mprPath/e.output replaced with ctx.* equivalents"
    
    So ctx.Output should exist and be an io.Writer.
    
    Another possibility: maybe in this specific function, they changed it to use ctx differently.
    
    Let me look at the full context of the applyDropBoundaryEvent call in cmd_alter_workflow.go from the diff:
    
    The diff shows:
    
      case *ast.DropBoundaryEventOp:
    
  •   	if err := applyDropBoundaryEvent(e, rawData, o); err != nil {
    
  •   	if err := applyDropBoundaryEvent(ctx, rawData, o); err != nil {
      		return mdlerrors.NewBackend("DROP BOUNDARY EVENT", err)
      	}
    
    
    And the function is defined as:
    
    +func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {
    
    This is definitely a type mismatch.
    
    Unless... ctx has a method that returns an io.Writer? But that seems unlikely.
    
    Perhaps ExecContext embeds something that is an io.Writer? But the PR says they replaced e.output with ctx.Output, suggesting Output is a field.
    
    Given that the PR says make build is clean, I suspect that in the actual code, it's correct, and the diff I'm seeing might be misleading due to truncation or I'm misreading.
    
    But based solely on the provided diff snippet, this looks like an error.
    
    However, let's look at the very end of the cmd_alter_workflow.go diff:
    
    
    @@ -865,7 +865,7 @@ func applyInsertBoundaryEvent(e *Executor, doc bson.D, op *ast.InsertBoundaryEve
    //
    // Limitation: this always removes events[0]. There is currently no syntax to
    // target a specific boundary event by name or type when multiple exist.
    -func applyDropBoundaryEvent(doc bson.D, op *ast.DropBoundaryEventOp) error {
    +func applyDropBoundaryEvent(w io.Writer, doc bson.D, op *ast.DropBoundaryEventOp) error {
    actDoc, err := findActivityByCaption(doc, op.ActivityRef, op.AtPosition)
    if err != nil {
    return err
    }
    
    And then the call site change is shown earlier.
    
    I think this is a genuine bug in the PR as presented in the diff.
    
    But to be fair, the diff was truncated, and maybe there's more context. However, based on what's provided, this is an issue.
    
    Another minor thing: in mdl/executor/cmd_catalog.go, there's a comment that says:
    
    // Create a goroutine-local executor: shared reader + cache, own output buffer.
    // TODO: Replace with ExecContext.Fork() when MR 3 makes ExecContext self-contained.
    And they changed it to:
    
    // Create a goroutine-local context: shared backend + cache, own output buffer.
    But in the code, they still create an Executor:
    
    var buf bytes.Buffer
    local := &Executor{
    reader: ctx.executor.reader,
    writer: nil,
    output: &buf,
    }
    So they're still using Executor, not ExecContext directly. The comment says they'll replace it with ExecContext.Fork() when available, which makes sense.
    
    This isn't a bug, just a TODO.
    
    Back to the potential bug in
    
    

Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

mdl/executor/hierarchy.go:128

  • getHierarchy now gates on ctx.Connected(), but it still dereferences ctx.executor/e.reader when the hierarchy cache isn’t already populated. This can panic for contexts constructed without an Executor (e.g., tests using MockBackend) or for future non-MPR backends where Backend is connected but e.reader is nil.

Consider either (a) building the hierarchy from ctx.Backend (ListModules/ListUnits/ListFolders) instead of e.reader, or (b) adding a defensive guard like if ctx.executor == nil || ctx.executor.reader == nil { return nil, mdlerrors.NewNotConnected() } before calling NewContainerHierarchy.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/cmd_catalog.go
Comment thread mdl/executor/format.go
Comment thread mdl/executor/cmd_styling.go
@retran retran force-pushed the feature/context-isolation branch from 974d94c to 9f02d5d Compare April 17, 2026 19:16
@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • Excellent refactoring execution: The PR successfully decouples handlers from direct mpr.Reader/mpr.Writer access by introducing the Backend interface and pushing dependencies into ExecContext. This is applied consistently across 66 files with a net reduction of 264 lines.

  • Improved testability: The introduction of MockBackend and mock-based tests for showEnumerations demonstrates the refactoring's goal of making handler logic more testable in isolation.

  • Proper interface segregation: The new CatalogReader interface cleanly separates the catalog builder's needs from the concrete *mpr.Reader implementation, allowing for better abstraction.

  • Consistent error handling: Replacing direct nil checks (e.reader == nil) with ctx.Connected()/ctx.ConnectedForWrite() methods provides clearer semantics and better error context.

  • Atomic commits: Each commit addresses a single, well-defined aspect of the refactor:

    1. Adding backend-dependent fields to ExecContext
    2. Replacing nil checks with connection methods
    3. Bulk conversion of field accesses
    4. Converting helper functions to free functions
    5. Introducing CatalogReader interface and mock tests
  • Preserved functionality: All verification steps pass (make build, make test, make lint clean), indicating no regressions were introduced.

  • Architectural alignment: The changes align with the project's goal of making the executor more modular and testable, as outlined in the architecture documentation.

Recommendation

Approve the pull request. This is a well-executed refactor that improves code quality, testability, and separation of concerns while maintaining full backward compatibility and passing all tests. The changes are consistently applied, properly scoped, and represent a significant step toward making the executor logic more modular and easier to test. No changes are needed.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

mdl/executor/executor.go:318

  • Executor.Close() now clears e.backend but does not close or clear other long-lived resources like the catalog. If a catalog was built/loaded, this leaves the DB handle open and may keep stale state around for any reuse. Suggestion: in Close(), if e.catalog != nil call e.catalog.Close() and set it to nil (and consider resetting ThemeRegistry as well).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/executor_connect.go
@retran retran force-pushed the feature/context-isolation branch from 9f02d5d to 29698c1 Compare April 17, 2026 19:42
@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

  • The refactor successfully decouples handlers from direct mpr.Reader/mpr.Writer access through the ExecContext.Backend interface, improving testability and separation of concerns
  • Mock-based tests demonstrate improved testability (showEnumerations handler tested with MockBackend)
  • Changes are consistently applied across 66 files with appropriate exceptions left for legitimate reasons (Executor method calls, shared state mutations, concrete type requirements)
  • All existing tests pass (make test — all pass), indicating no behavioral changes
  • Thoughtful migration support via the Wrap function in MprBackend allowing existing Writer ownership to be exposed through Backend interface
  • Clean interface design with CatalogReader abstraction allowing catalog to work with either direct reader or backend abstraction
  • Proper handling of connection state through Connected()/ConnectedForWrite() methods replacing manual nil checks
  • Correct updates to error handling and output routing (e.g., in alter_workflow warning messages now properly routed through ctx.Output)

Recommendation

Approve. This is a well-executed refactor that improves the codebase's structure and testability without changing behavior. The PR is properly scoped to a single refactoring goal, consistently implemented, and all verification checks pass. The refactor continues the series logically (#222#224#225 → this PR) and maintains backward compatibility while enabling better unit testing through interface abstraction.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

mdl/executor/hierarchy.go:123

  • When initializing the cache, this writes through to e.cache without checking whether ctx.executor is non-nil. Since ExecContext can be constructed without an Executor (see mock-based tests), add a nil-check before assigning to e.cache (or stop syncing cache to Executor in this helper).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/hierarchy.go
Comment thread mdl/executor/cmd_rename.go Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 81 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mdl/executor/hierarchy.go
Comment thread mdl/executor/cmd_catalog.go Outdated
…tput threading, disconnect ctx cleanup, cache invalidation, theme registry persistence, parallel ctx fields, settings persistence, structured logging, nil-safety guards, project-scoped cache reset, backend-based hierarchy, enum ref error handling
@retran retran force-pushed the feature/context-isolation branch from 29698c1 to 7fe3fb5 Compare April 17, 2026 19:59
@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

This PR continues the executor refactoring series successfully:

  • Consistent decoupling: Replaced ~550 direct e.reader/e.writer calls with ctx.Backend across 66 files, reducing coupling between handlers and concrete MPR implementations
  • Improved testability: Introduced CatalogReader interface and mock-based tests demonstrating better isolation
  • Clean abstractions: Added Connected()/ConnectedForWrite() methods replacing 154 nil checks
  • Free function conversions: Moved getEntityNames, getMicroflowNames, etc. to free functions using ctx.Backend/ctx.Cache
  • Transitional support: Added Wrap() function to maintain compatibility during migration
  • Verified quality: All build, test, and lint checks pass

The refactoring follows the established pattern from previous PRs (#222, #224, #225) and maintains backward compatibility while improving the architecture.

Recommendation

Approve. This PR successfully advances the executor refactoring goals with minimal risk, good test coverage, and consistent application of the refactoring pattern across the codebase. No blocking issues were identified in the review.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako
Copy link
Copy Markdown
Collaborator

ako commented Apr 17, 2026

Review

This is the PR the stack needed. 531 ctx.Backend calls across 66 files, mock-based tests proving the pattern works, CatalogReader interface decoupling the catalog — all the things I asked for in my #225 review. The architecture is no longer faith-based.

What I like

  • Backend is now proven in practice. 531 ctx.Backend.XXX() calls replace e.reader/e.writer across every domain — modules, entities, enumerations, microflows, pages, security, navigation, OData, REST, workflows, agent editor. This isn't a toy migration; it's the real surface area.
  • Mock-based test validates the pattern. TestShowEnumerations_Mock constructs a MockBackend with function fields, wires it into an ExecContext, calls the handler, asserts the output. TestShowEnumerations_Mock_FilterByModule tests the filter path. This is exactly the proof that MockBackend is usable and that handlers can be tested without a .mpr file.
  • Connected()/ConnectedForWrite() replace ~154 nil checks. Clean semantic methods instead of if e.reader == nil. When the backend changes (e.g., read-only mode, remote backend), these methods absorb the difference.
  • CatalogReader interface is well-scoped. Both *mpr.Reader and backend.FullBackend satisfy it — the catalog builder doesn't know which one it's talking to. This is the right abstraction for the catalog's needs.
  • ~27 e := ctx.executor remain, documented and justified. Down from 300 in refactor: migrate executor handlers to free functions with ExecContext #225. The remaining sites are lifecycle (Execute, ExecuteProgram), shared state (trackModifiedDomainModel, catalog, cache), and concrete-type requirements (pageBuilder, lint). Honest about what's left.
  • No go fmt noise. Two PRs in a row. Good discipline.
  • 5 incremental commits that map cleanly to the scope: ExecContext fields → connection checks → bulk migration → helper conversions → CatalogReader + mock tests.

Concerns

  1. CI is still running. Build-and-test not yet concluded. The diff is large enough that I'd want green before merge. (If it passes, this concern is moot.)

  2. Only one handler has a mock test. showEnumerations proves the pattern, but the real value of MockBackend comes when more handlers get tested this way — especially write-path handlers (create/alter/drop) where testing without a .mpr is most valuable. Worth a follow-up ticket: "add mock tests for 5 write-path handlers."

  3. InvalidateCache() writes to both ctx.Cache and ctx.executor.cache. Dual-state management that could desync if one is nil-checked before the other. Documented as temporary, and it resolves when executor is removed, but worth a comment on the invalidation order.

  4. ConnectedForWrite() is currently identical to Connected(). The comment says "MprBackend always supports writes." If a read-only backend is introduced, callers of ConnectedForWrite() are already doing the right check. But there's no test that exercises the false case for write-specific checks. Low risk, just noting.

  5. CatalogReader includes ListRawUnitsByType and GetRawUnit — these are mpr-specific methods (raw BSON units). If a future non-mpr backend doesn't have raw units, it would need to stub these. Consider whether the interface should be narrower (only typed list methods) with raw access as a separate optional interface. Not blocking — the current shape works for both implementations.

Verdict

Approve. This delivers on the promise of #222/#224/#225: handlers are decoupled from mpr, MockBackend is validated, and the path to removing the remaining 27 e := ctx.executor sites is clear. Wait for CI green, then merge.

@ako ako merged commit 321c7fd into mendixlabs:main Apr 17, 2026
2 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.

3 participants