feat(lint): expose scheduled_events() query function in Starlark rules#692
Conversation
AI Code ReviewReview SummaryModerate Issues
Minor Issues
What Looks Good
RecommendationRequest 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 |
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor Issues
What Looks Good
RecommendationApprove. 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 |
|
Verified end-to-end on the branch. What's solid:
🔴 Blockers / should-fix:
Minor:
Recommendation: request changes — rebase to clear the conflict, and while resolving, flip |
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
492fe20 to
3ee05b7
Compare
AI Code ReviewOkay, I need to review this pull request for the mxcli project. The PR adds a new Let me go through the PR details and check against the review checklist provided in CLAUDE.md. First, the PR body says:
Now, I'll review against the checklist: Overlap & duplication
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:
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
Looking at the test file, it appears comprehensive:
Security & robustness
In 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.
Scope & atomicity
Code quality
Let me double-check some specifics:
Now, let me check if there are any issues: Potential issue: In 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 Actually, looking more closely at the 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 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 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 Actually, in the provided diff, I don't see other database queries in this file, but in the test they're using This is a potential bug: the code is querying If the actual table names are However, since this is a new feature and the tests are passing (they set up the data in
But looking at the test, it specifically inserts into Wait, no - in the test, they're setting up data in But in the test // 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 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 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 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 In 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 Therefore, the code must be querying the But in the diff shown, the code queries This suggests that either:
Since I can only review what's in the diff, I have to go by what's shown. In the diff for 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 This is likely a bug. The code should be querying 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 But wait - looking at the catalog package might help. The test uses Without seeing the catalog code, it's hard to say, but it's common for such systems to have Another possibility: the But the test explicitly inserts into 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 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 If the code queries 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 Since the diff shows the code querying non- 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 However, to be fair, I should note that in many database schemas, the main tables might not have But the test is very explicit about using Another angle: in the test, after inserting into Given all this, I suspect there's a typo in the PR: the Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Re-reviewed the update — all three blockers from my earlier review are resolved, verified on the rebased branch:
Verification: Remaining (minor, non-blocking, unchanged): the two Recommendation: approve. The review items (conflict, |
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).LintReaderwithListScheduledEvents()(already implemented by MprBackend and MockBackend — no concrete changes needed there)ScheduledEventstruct andScheduledEvents() iter.SeqonLintContext; resolves module names from the catalogmodulestable and converts interval+type to seconds viaintervalToSeconds()scheduled_eventsbuiltin and addsscheduledEventToStarlarkconversion instarlark.go