Skip to content

feat(lint): expose scheduled_events() query function in Starlark rules#692

Merged
ako merged 3 commits into
mendixlabs:mainfrom
Andries-Smit:feat/lint-scheduledevents-query-function
Jun 26, 2026
Merged

feat(lint): expose scheduled_events() query function in Starlark rules#692
ako merged 3 commits into
mendixlabs:mainfrom
Andries-Smit:feat/lint-scheduledevents-query-function

Conversation

@Andries-Smit

Copy link
Copy Markdown
Contributor

Adds a new scheduled_events() built-in to the linter's Starlark API so star rules can inspect scheduled event configuration (name, microflow, interval, enabled flag).

  • Extends LintReader with ListScheduledEvents() (already implemented by MprBackend and MockBackend — no concrete changes needed there)
  • Adds ScheduledEvent struct and ScheduledEvents() iter.Seq on LintContext; resolves module names from the catalog modules table and converts interval+type to seconds via intervalToSeconds()
  • Registers scheduled_events builtin and adds scheduledEventToStarlark conversion in starlark.go

@github-actions

Copy link
Copy Markdown

AI Code Review

Review Summary

Moderate Issues

  • Missing test coverage: The PR adds new linter functionality (scheduled_events() Starlark built-in) but includes no test files. According to the checklist, new functionality should have test coverage. Unit tests are needed for:
    • ScheduledEvent struct
    • intervalToSeconds() function
    • ScheduledEvents() method on LintContext
    • Starlark built-in integration

Minor Issues

  • Error handling in intervalToSeconds: Returns 0 for unknown interval types without logging or indication of error. Consider at least documenting this behavior or returning a default with warning.
  • MicroflowName assignment: Sets MicroflowName: string(e.MicroflowID) but MicroflowID is likely a model.ID. Verify this produces the expected qualified name (other similar fields in codebase may provide guidance).

What Looks Good

  • Clean implementation following existing linter patterns (similar to Snippets() method)
  • Proper nil reader handling and error checking
  • Correct catalog usage for module name resolution (moduleName + "." + e.Name)
  • Consistent exclusion pattern (ctx.excluded[moduleName] check)
  • Starlark integration cleanly follows existing built-in patterns
  • Struct includes all relevant fields (name, qualified_name, module_name, microflow_name, interval_seconds, enabled)

Recommendation

Request changes to add appropriate test coverage for the new functionality. The implementation is fundamentally sound and follows project conventions, but lacks the required tests per the checklist. Once tests are added, this PR would be ready for approval.


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

@github-actions

Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • Potential integer overflow in intervalToSeconds: The function intervalToSeconds(interval int, intervalType string) int multiplies interval by large constants (e.g., 31536000 for years) without overflow checking. While Mendix scheduled events typically use small interval values (minutes, hours, days), extremely large values could cause integer overflow and incorrect results. This is low-risk but worth noting.

What Looks Good

  • Comprehensive test coverage: The new test file starlark_scheduledevents_test.go thoroughly tests interval conversion, microflow name resolution, excluded modules, nil reader handling, and Starlark built-in functionality.
  • Clean implementation: The code follows established patterns in the linter package, properly extending LintReader and LintContext with minimal, focused changes.
  • Proper fallback behavior: When catalog queries fail, the code gracefully falls back to using raw IDs rather than crashing.
  • Correct Starlark integration: The built-in function is properly registered and converts ScheduledEvent structs to Starlark values with all relevant fields.
  • No MDL syntax changes: As this is a linter/Starlark API extension (not an MDL feature), it correctly avoids touching the MDL pipeline (grammar, AST, visitor, executor).
  • Scoped changes: The PR is focused on a single feature - exposing scheduled events to Starlark rules - with no unrelated modifications.

Recommendation

Approve. The PR is well-implemented with excellent test coverage and follows project conventions. The minor integer overflow concern is extremely low-risk given the expected values for scheduled event intervals in Mendix applications. No changes are required before merging.


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

@ako

ako commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Verified end-to-end on the branch.

What's solid:

  • Correct interval mapping. The intervalToSeconds keys match the real metamodel enum ScheduledEventsIntervalType (generated/metamodel/enums.go:1764) exactly — Second/Minute/Hour/Day/Week/Month/Year — so there's no silent interval_seconds: 0 bug. (Month=30d, Year=365d are calendar approximations, inherent and fine for a lint heuristic.)
  • Data path fully wired. All fields used (ContainerID/Name/MicroflowID/Interval/IntervalType/Enabled) exist on model.ScheduledEvent; backends populate IntervalType from BSON (parser_enumeration.go:205, modelsdk/scheduledevent_read.go:58); and ListScheduledEvents() is implemented across MPR/modelsdk/mock, so the new LintReader method is satisfied everywhere.
  • Excellent tests. Covers all 7 interval units + 2 unknown-type fallbacks, microflow-name resolution (catalog hit and raw-UUID fallback), excluded modules, nil reader, and a full Starlark integration test. All pass.

🔴 Blockers / should-fix:

  1. Merge conflict (CONFLICTING). Simulated the merge — the only conflict is in mdl/linter/starlark.go: adding scheduled_events widened the longest key, so gofmt re-aligned the whole "Query functions" map, colliding with feat(lint): expose constants() query function to Starlark lint rules #685's constants insertion. Mechanical to resolve (keep all builtins, re-gofmt), but needs a rebase.
  2. On rebase, switch to ctx.IsExcluded(moduleName). ScheduledEvents() uses the old ctx.excluded[moduleName] (context.go:733). The branch predates feat(lint): add --modules/-m flag to scope lint to specific modules #684, so it looks consistent here — but main now has all 8 iterators on the centralized ctx.IsExcluded(...), which also honors the --modules filter. After rebase, ScheduledEvents() would be the lone iterator still reading the map directly, silently ignoring -m (same skew that bit feat(lint): expose constants() query function to Starlark lint rules #685). TestScheduledEvents_ExcludedModules only exercises the exclude path, so it won't catch this — worth an include-filter (SetIncludedModules) assertion too.
  3. No skill docs. All 3 changed files are under mdl/linter/; .claude/skills/mendix/write-lint-rules.md isn't updated (same gap as feat(lint): expose XPath/expression AST to Starlark rules #688). The new scheduled_events() builtin and the scheduled_event object (6 fields) should go in the "Available Query Functions" table + a property table, especially since that skill is synced into user projects.

Minor:

  • Both defer rows.Close() defer to the end of the iterator closure, so the modules result set stays open while the microflows query runs. Works here (pooled file-based catalog, tests pass), but closing each promptly would be cleaner.
  • intervalToSeconds returns 0 for an unrecognized type with no signal; harmless given the keys are exhaustive against the enum, but a rule could misread interval_seconds == 0.

Recommendation: request changes — rebase to clear the conflict, and while resolving, flip ScheduledEvents() to ctx.IsExcluded() (+ an include-filter test) and add the skill docs. The core implementation and tests are genuinely strong; these are integration/consistency items, not logic defects.

Andries-Smit and others added 3 commits June 26, 2026 10:32
Adds a new `scheduled_events()` built-in to the linter's Starlark API so
star rules can inspect scheduled event configuration (name, microflow,
interval, enabled flag).

- Extends `LintReader` with `ListScheduledEvents()` (already implemented
  by MprBackend and MockBackend — no concrete changes needed there)
- Adds `ScheduledEvent` struct and `ScheduledEvents() iter.Seq` on
  `LintContext`; resolves module names from the catalog `modules` table
  and converts interval+type to seconds via `intervalToSeconds()`
- Registers `scheduled_events` builtin and adds `scheduledEventToStarlark`
  conversion in `starlark.go`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

- Resolve MicroflowID UUID to qualified name via catalog microflows table,
  falling back to raw UUID when catalog is not built
- Document intervalToSeconds zero-return for unrecognised interval types
- Add 5 tests: intervalToSeconds, MicroflowName resolution, excluded modules,
  nil reader, and Starlark builtin end-to-end

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8txDneDb92yxMgD2LCPkR
…ter test, skill docs

- Use ctx.IsExcluded(moduleName) in ScheduledEvents() so --modules/-m
  inclusion filter is honoured (was reading ctx.excluded map directly,
  silently ignoring the include list added in mendixlabs#684)
- Add TestScheduledEvents_IncludedModules to cover the SetIncludedModules
  path that the existing TestScheduledEvents_ExcludedModules missed
- Document scheduled_events() in write-lint-rules.md: entry in the
  Available Query Functions table + scheduled_event property table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V64Soim57zvKW4m7dafQUW
@Andries-Smit Andries-Smit force-pushed the feat/lint-scheduledevents-query-function branch from 492fe20 to 3ee05b7 Compare June 26, 2026 08:35
@github-actions

Copy link
Copy Markdown

AI Code Review

Okay, I need to review this pull request for the mxcli project. The PR adds a new scheduled_events() built-in function to the linter's Starlark API so that Starlark rules can inspect scheduled event configuration.

Let me go through the PR details and check against the review checklist provided in CLAUDE.md.

First, the PR body says:

  • Adds a new scheduled_events() built-in to the linter's Starlark API so star rules can inspect scheduled event configuration (name, microflow, interval, enabled flag).
  • Extends LintReader with ListScheduledEvents() (already implemented by MprBackend and MockBackend — no concrete changes needed there)
  • Adds ScheduledEvent struct and ScheduledEvents() iter.Seq on LintContext; resolves module names from the catalog modules table and converts interval+type to seconds via intervalToSeconds()
  • Registers scheduled_events builtin and adds scheduledEventToStarlark conversion in starlark.go

Now, I'll review against the checklist:

Overlap & duplication

  • Check proposals list above for existing proposals covering the same functionality: The document says "No proposals directory." so no existing proposals to check.
  • Check if the diff introduces code that duplicates existing implementations: Looking at the diff, it adds new functions and structs for scheduled events. I don't see obvious duplication with existing code for other entities like entities, microflows, etc.
  • Check doctype test files above for existing test coverage of the feature area: The doctype test files listed don't include anything about scheduled events specifically, though there are many test files. The PR adds a new test file starlark_scheduledevents_test.go which seems appropriate.
  • Flag if the PR re-documents already-shipped features as new: This appears to be adding new functionality, not re-documenting existing.

MDL syntax design (for new or modified MDL syntax)

The PR is about adding a Starlark built-in function for the linter, not about modifying MDL syntax itself. The PR doesn't change any MDL grammar, AST, visitor, executor, etc. It's purely adding a new query function to the Starlark API used in lint rules.

Looking at the checklist for MDL syntax design:

  • It's not adding MDL syntax, so this section doesn't apply directly.
  • However, the PR does update documentation in .claude/skills/mendix/write-lint-rules.md to document the new scheduled_events() function.

Since this is about the linter's Starlark API and not MDL syntax, I should focus on whether the changes follow the patterns for similar existing functions.

Full-stack consistency (for MDL features)

This is important: the PR is NOT adding an MDL feature. It's adding a Starlark built-in function for the linter. So the full-stack consistency requirements for MDL features (grammar → AST → visitor → executor → etc.) do not apply here.

The checklist says: "New MDL commands or language features must be wired through the full pipeline." But this is not an MDL command or language feature - it's a linter API extension.

Test coverage

  • New packages should have test files: The PR adds a new test file mdl/linter/starlark_scheduledevents_test.go which tests the new functionality.
  • New executor commands should have MDL examples in mdl-examples/doctype-tests/: Not applicable since this is not an executor command.
  • Integration paths should be tested, not just helpers: The test file seems to test the integration path through the linter context.
  • No time.Sleep for synchronization: I don't see any time.Sleep in the test file.

Looking at the test file, it appears comprehensive:

  • Tests interval conversion
  • Tests microflow name resolution (with and without catalog)
  • Tests excluded modules
  • Tests included modules
  • Tests nil reader
  • Tests the Starlark built-in function

Security & robustness

  • Command injection, XSS, SQL injection, temp file issues: I don't see obvious vulnerabilities. The SQL queries use parameterized queries (though looking at the code, it seems they're using string formatting for the SQL? Wait, let me check).

In context.go, I see:

if rows, err := ctx.db.Query(`SELECT Id, Name FROM modules`); err == nil {

and

if rows, err := ctx.db.Query(`SELECT Id, QualifiedName FROM microflows`); err == nil {

These are static SQL queries with no user input, so no SQL injection risk.

  • Unix sockets should use restrictive permissions (0600): Not applicable here.
  • File I/O should not be in hot paths: Not applicable.
  • No silent side effects on typos: The function names are clear and match existing patterns.
  • Correct method receivers: The new methods on LintContext use pointer receivers appropriately.

Scope & atomicity

  • Each commit should do one thing: The PR seems focused on adding the scheduled_events() built-in.
  • PR should be scoped to a single feature or concern: Yes, it's all about adding this one function.
  • Independent features should be in separate PRs: Not applicable.
  • Refactors touching many files should be their own commit: Not a refactor.

Code quality

  • Refactors should be applied consistently: Not a refactor.
  • Manually maintained lists should be flagged: I don't see any manually maintained lists being modified.
  • Design docs should match the actual implementation: The documentation in .claude/skills/mendix/write-lint-rules.md matches what's implemented.

Let me double-check some specifics:

  1. In context.go, the ScheduledEvents() method:

    • It builds module names and microflow names maps from the catalog
    • It calls ctx.reader.ListScheduledEvents()
    • It processes each event, converting interval to seconds
    • It checks if the module is excluded
    • It yields each ScheduledEvent

    This follows the same pattern as other similar methods like Snippets().

  2. The intervalToSeconds function:

    • It maps interval types to multipliers
    • Returns 0 for unrecognized types
    • This matches the pattern used elsewhere in the codebase for similar conversions.
  3. In starlark.go:

    • It adds the scheduled_events builtin to the predeclared functions
    • It implements builtinScheduledEvents which iterates over ctx.ScheduledEvents() and converts each to a Starlark struct
    • It adds scheduledEventToStarlark helper function

    This follows the exact same pattern as other builtins like entities, microflows, etc.

  4. The test file is thorough and tests various edge cases.

Now, let me check if there are any issues:

Potential issue: In context.go, the SQL queries:

if rows, err := ctx.db.Query(`SELECT Id, Name FROM modules`); err == nil {

and

if rows, err := ctx.db.Query(`SELECT Id, QualifiedName FROM microflows`); err == nil {

These queries don't handle the case where the query fails (they just ignore the error and proceed with empty maps). But looking at other similar code in the file, this seems to be the established pattern. For example, in the Snippets() method, I see similar error handling.

Actually, looking more closely at the Snippets() method in context.go (which exists in the current code), it doesn't even try to query the database - it just returns snippets from the reader. So for scheduled events, since we need to resolve names from the catalog, we do need to query the database.

But the error handling here is: if the query fails, we just skip building the map and proceed (so moduleName and mfName will be empty or fallback to UUIDs). This seems acceptable as a fallback behavior.

Another thing: the query for modules is SELECT Id, Name FROM modules but in the test, they're inserting into modules_data. Let me check if this is correct.

In the test file, I see:

if _, err := db.Exec(
	`INSERT INTO modules_data (Id, Name, ProjectId, SnapshotId) VALUES (?,?,?,?)`,
	string(containerID), "MyModule", "default", "s1",
); err != nil {
	t.Fatalf("insert module: %v", err)
}

And in the code:

if rows, err := ctx.db.Query(`SELECT Id, Name FROM modules`); err == nil {

There's a discrepancy: the test uses modules_data but the code queries modules. This might be an issue.

Let me check the actual schema. Since I don't have the full codebase, I need to infer from context.

Looking at other parts of the linter code, for example in the same file, the Snippets() method doesn't use the database at all. But for methods that do, like perhaps DatabaseConnection, let me see if I can find examples.

Actually, in the provided diff, I don't see other database queries in this file, but in the test they're using modules_data and microflows_data.

This is a potential bug: the code is querying modules and microflows tables, but the tests are inserting into modules_data and microflows_data.

If the actual table names are _data suffixed, then the queries will return no results, causing the module and microflow name resolution to fall back to UUIDs (which might be acceptable but not ideal).

However, since this is a new feature and the tests are passing (they set up the data in _data tables but the code queries non-_data tables), this suggests that either:

  1. The actual table names don't have _data suffix, or
  2. There's a mistake.

But looking at the test, it specifically inserts into modules_data and microflows_data, and then the test passes, which means the code must be querying those tables.

Wait, no - in the test, they're setting up data in _data tables, but if the code queries tables without _data, then the queries would return no results, and the test would fail because it expects the qualified names to be resolved.

But in the test TestScheduledEvents_MicroflowNameResolution, it checks:

// When the microflow ID is in the catalog, MicroflowName must be the qualified name.
if got := events["SEWithCatalog"].MicroflowName; got != "MyModule.ACT_DoSomething" {
	t.Errorf("SEWithCatalog.MicroflowName = %q, want %q", got, "MyModule.ACT_DoSomething")
}

For this to pass, the query must have found the microflow in the catalog.

Therefore, the table names in the code must match what's used in the test.

Let me look back at the code in the diff:

In context.go:

if rows, err := ctx.db.Query(`SELECT Id, Name FROM modules`); err == nil {

and

if rows, err := ctx.db.Query(`SELECT Id, QualifiedName FROM microflows`); err == nil {

But in the test, they're using:

`INSERT INTO modules_data (Id, Name, ProjectId, SnapshotId) VALUES (?,?,?,?)`

and

`INSERT INTO microflows_data (Id, Name, QualifiedName, ModuleName, MicroflowType, ProjectId, SnapshotId) ...`

This is inconsistent. Either the code should query modules_data and microflows_data, or the test should insert into modules and microflows.

Given that the test is part of the PR and is meant to validate the functionality, and assuming the test is correct, then the code has a bug: it's querying the wrong table names.

However, I notice that in the test setup for TestIntervalToSeconds, they do:

if _, err := db.Exec(
	`INSERT INTO modules_data (Id, Name, ProjectId, SnapshotId) VALUES (?,?,?,?)`,
	string(containerID), "MyModule", "default", "s1",
); err != nil {
	t.Fatalf("insert module: %v", err)
}

And then in the test, they expect the module name to be resolved correctly (since they check se.ModuleName).

In TestIntervalToSeconds, they have:

containerID := model.ID("mod-1")
// ...
cat, err := catalog.NewFromFile(filepath.Join(t.TempDir(), "cat.db"))
if err != nil {
	t.Fatalf("NewFromFile: %v", err)
}
db := cat.CatalogDB()
// ...
if _, err := db.Exec(
	`INSERT INTO modules_data (Id, Name, ProjectId, SnapshotId) VALUES (?,?,?,?)`,
	string(containerID), "MyModule", "default", "s1",
); err != nil {
	t.Fatalf("insert module: %v", err)
}

Then later:

ctx := linter.NewLintContext(cat, reader)
var got int
for se := range ctx.ScheduledEvents() {
	got = se.IntervalSeconds
}

For se.ModuleName to be correct (which it needs to be for QualifiedName to be right), the module name lookup must have worked.

Therefore, the code must be querying the _data tables.

But in the diff shown, the code queries modules and microflows, not modules_data and microflows_data.

This suggests that either:

  1. The diff is incomplete/misrepresented, or
  2. There's a mistake in the code.

Since I can only review what's in the diff, I have to go by what's shown.

In the diff for context.go:

		if rows, err := ctx.db.Query(`SELECT Id, Name FROM modules`); err == nil {
			defer rows.Close()
			for rows.Next() {
				var id, name string
				if rows.Scan(&id, &name) == nil {
					moduleNames[model.ID(id)] = name
				}
			}
		}
		if rows, err := ctx.db.Query(`SELECT Id, QualifiedName FROM microflows`); err == nil {
			defer rows.Close()
			for rows.Next() {
				var id, qname string
				if rows.Scan(&id, &qname) == nil {
					microflowNames[id] = qname
				}
			}
		}

But in the test, they're inserting into modules_data and microflows_data.

This is likely a bug. The code should be querying modules_data and microflows_data to match where the data is stored.

However, I should check if there's a pattern elsewhere in the codebase.

Since I don't have the full context, but given that the test is written to use _data tables and expects it to work, and the PR includes this test, it's probable that the actual implementation should use _data tables.

But wait - looking at the catalog package might help. The test uses catalog.NewFromFile and then cat.CatalogDB().

Without seeing the catalog code, it's hard to say, but it's common for such systems to have _data tables for the actual data.

Another possibility: the catalog.CatalogDB() returns a database connection where the tables are already named without _data, or there's a view.

But the test explicitly inserts into _data tables, so if the code queries non-_data tables, it won't see the data.

Given that the test is part of the PR and is meant to validate the functionality, and assuming the test is correct, then the code as shown in the diff has a bug.

However, let me double-check the diff. Is it possible that the table names in the code are correct and the test is wrong?

In the test for TestScheduledEvents_MicroflowNameResolution:

	if _, err := db.Exec(
		`INSERT INTO microflows_data (Id, Name, QualifiedName, ModuleName, MicroflowType, ProjectId, SnapshotId)
		 VALUES (?,?,?,?,?,?,?)`,
		string(mfID), "ACT_DoSomething", "MyModule.ACT_DoSomething", "MyModule", "Microflow", "default", "s1",
	); err != nil {
		t.Fatalf("insert microflow: %v", err)
	}

And then it checks that MicroflowName is "MyModule.ACT_DoSomething".

If the code queries microflows (without _data), and the data is in microflows_data, then the query would return no rows, mfName would be empty, and it would fall back to the UUID string.

But the test expects it to be the qualified name, so for the test to pass, the query must find the data.

Therefore, the code must be querying the _data tables.

Since the diff shows the code querying non-_data tables, this appears to be an error in the PR.

But wait - let's look at the very beginning of the context.go diff. Is there more context?

The diff starts at line 731, but maybe there are imports or other context I'm missing.

Actually, the diff shows:

@@ -731,6 +732,97 @@ func (ctx *LintContext) Snippets() iter.Seq[Snippet] {
 	}
 }
 
+// ScheduledEvent represents a scheduled event document.
+type ScheduledEvent struct {
+	// ...
+}
+
+// ... etc ...

So it's adding new code after the Snippets method.

I don't see any other database queries in this snippet, but perhaps in the existing code there are examples.

Since I don't have the full file, I can't be sure, but given the test setup, it's likely that the table names should include _data.

However, to be fair, I should note that in many database schemas, the main tables might not have _data, and _data could be for something else.

But the test is very explicit about using _data.

Another angle: in the test, after inserting into modules_data, they close the catalog and then create a new LintContext. If the catalog database is file-based, and they're inserting into the same file, then the table names must match.

Given all this, I suspect there's a typo in the PR: the


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

@ako

ako commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Re-reviewed the update — all three blockers from my earlier review are resolved, verified on the rebased branch:

  1. Merge conflict resolved. Properly rebased onto current main (merge-base == main head); the starlark.go builtins block re-gofmt'd cleanly, keeping constants and adding scheduled_events. CLEAN/MERGEABLE.
  2. ctx.IsExcluded(moduleName) now used in ScheduledEvents() (was the direct ctx.excluded[...]), so it's consistent with the other iterators and honors --modules.
  3. Include-filter test addedTestScheduledEvents_IncludedModules seeds two modules, calls SetIncludedModules(["ModA"]), and asserts exactly [ModA], exercising the path the old exclude-only test missed.
  4. Skill docs addedwrite-lint-rules.md now lists scheduled_events() in the Available Query Functions table plus a ### scheduled_event property table. Checked against the implementation: all 6 fields documented, correctly noting the microflow_name catalog/raw-UUID fallback and interval_seconds: 0 for unrecognised interval types.

Verification: go vet clean, full ./mdl/linter suite green, all 6 scheduled-event tests pass (incl. the new include-filter one). Interval mapping still matches the real ScheduledEventsIntervalType enum.

Remaining (minor, non-blocking, unchanged): the two defer rows.Close() in ScheduledEvents() still defer to the end of the iterator closure, so the modules result set stays open during the microflows query. Works (tests pass on the pooled file-based catalog) — purely a cleanliness nit.

Recommendation: approve. The review items (conflict, --modules consistency + test, docs) are all properly addressed and verified.

@ako ako merged commit d1b17a4 into mendixlabs:main Jun 26, 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.

2 participants