refactor: introduce LintReader interface, decouple linter from sdk/mpr#289
refactor: introduce LintReader interface, decouple linter from sdk/mpr#289ako merged 2 commits intomendixlabs:mainfrom
Conversation
AI Code ReviewCritical IssuesNone Moderate IssuesNone Minor IssuesNone What Looks Good
RecommendationApprove. The refactor cleanly achieves its goal of decoupling linter/executor from SQLite-specific code while maintaining full functionality and enabling WASM builds. The changes are focused, consistent, and follow Go interface best practices. No modifications to MDL syntax or pipeline were needed (as this is purely a backend abstraction refactor). Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
d7f88c1 to
1d67cb5
Compare
AI Code ReviewCritical Issues
Moderate Issues
Minor Issues
What Looks Good
Recommendation Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Thanks for the review — no issues found, agreed on all points. The |
|
Good catch on the The
The shim exists solely for any out-of-tree callers that might reference Re: |
ako
left a comment
There was a problem hiding this comment.
Code Review — PR #289
Overview: Introduces LintReader interface in mdl/linter/context.go to decouple linter and executor from *mpr.Reader. Clean, minimal diff — the 10+ rule files need no changes because Go's implicit interface satisfaction does the work. The direction is correct.
Good
- Zero
sdk/mprimports remain inmdl/linter/andmdl/executor/production code — stated goal achieved MockBackendalready satisfiesLintReader(all 7 methods present), so linter rules are immediately testable with mocks- Moving
IsAtLeasttomdl/types.ProjectVersionis the right direction —mdl/code should usetypes, notsdk/mpr/version - Constructor injection (
NewLintContext(cat, reader)) is cleaner than the old two-stepNew+SetReader
Issues
1. Missing compile-time assertion for LintReader
There's no var _ linter.LintReader = (*mpr.MprBackend)(nil) check anywhere. If a future PR removes or renames one of the 7 interface methods in the backend, the breakage is silent until a linter rule calls it at runtime. Add to mdl/backend/mpr/backend.go:
var _ linter.LintReader = (*MprBackend)(nil)2. Reader() shim is a silent API break
Executor.Reader() changes return type from *mpr.Reader to backend.FullBackend. The comment says "backward compatibility" but the signature changed — callers that stored the result as *mpr.Reader will fail to compile. This isn't backward compatible; it's a type replacement. The shim gives false confidence. Either drop it (it was already Deprecated) or keep the old signature and panic/log if someone calls it (clearer failure mode).
3. Implicit nil reader passed to NewLintContext
Previously, cmd_lint.go and cmd_report.go had an explicit nil guard before calling SetReader:
if reader := exec.Reader(); reader != nil {
ctx.SetReader(reader)
}Now exec.Backend() (which returns nil when not connected) is passed unconditionally to NewLintContext. Rules already need to nil-check ctx.Reader(), so this is safe in practice — but worth a comment on NewLintContext that reader may be nil and rules must guard accordingly.
4. IsAtLeast now exists on two distinct types
mdl/types.ProjectVersion.IsAtLeast (new) and sdk/mpr/version.ProjectVersion.IsAtLeast (existing) are separate implementations on separate types with the same logic. There's no deduplication plan. At minimum, add a // TODO: remove once all callers use types.ProjectVersion note to sdk/mpr/version to track the migration.
Minor
roundtrip_helpers_test.go:599callse.executor.Reader().ProjectVersion()— this now calls throughbackend.FullBackend.ProjectVersion()returning*types.ProjectVersion, which hasIsAtLeastfrom this PR. Works, but the indirection via the deprecatedReader()shim should be updated toe.executor.Backend().ProjectVersion()for clarity.LintReaderis defined incontext.gobut semantically belongs closer to an interface file. Minor — fine as-is given the package size.
Overall: Approve with the compile-time assertion added (#1) — it's a one-liner and guards against silent future regressions. The Reader() shim issue (#2) is worth fixing before merge to avoid confusing external callers.
retran
left a comment
There was a problem hiding this comment.
Thanks for the review! All items addressed in ab43ea1:
- Compile-time assertion — added
var _ linter.LintReader = (*MprBackend)(nil)alongside existingFullBackendassertion. - Reader() shim — removed the deprecated method entirely. Only 2 test callers existed; updated both to use
Backend(). - Nil reader comment — added doc on
NewLintContext: "reader may be nil; rules that require backend access must check Reader() != nil." - IsAtLeast dedup — went with the shared type approach:
sdk/mpr/version.ProjectVersionis now a type alias fortypes.ProjectVersion. MovedIsAtLeastFull,String,IsMPRv2totypes.ProjectVersion. ConvertedIsSupported/SupportsFeatureto package-level functions (can't add methods to type alias). RemovedconvertProjectVersionand its tests since the types are now identical. - Minor — updated test callers to use
Backend().ProjectVersion().
AI Code ReviewWhat Looks GoodThis PR successfully introduces the
The implementation correctly:
RecommendationApprove - This is a clean, focused refactor that improves the codebase's modularity and testability without changing behavior. It successfully addresses the WASM build blocker and enables mock backend testing as intended. All checklist items are either satisfied or not applicable to this refactor. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ab43ea1 to
fac023a
Compare
AI Code ReviewWhat Looks Good
RecommendationApprove - This refactor cleanly achieves its goal of decoupling the linter and executor packages from the SDK's concrete SQLite implementation while maintaining all existing functionality. The interface-based approach follows Go best practices and enables the requested WASM build support and improved testability. All checklist items are satisfied for this type of internal refactor. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ako
left a comment
There was a problem hiding this comment.
Code Review — PR #289 (updated review)
Overview: Decouples linter and executor from *mpr.Reader by introducing LintReader interface. This is a substantially stronger version than the original draft — all three main issues from the earlier review have been addressed.
What's improved since the first review
- Compile-time assertion added —
var _ linter.LintReader = (*MprBackend)(nil)inmdl/backend/mpr/backend.go✓ Reader()shim removed entirely —Executor.Reader()is gone; callers migrated toBackend()directly ✓- Nil reader documented —
NewLintContextcomment notesreadermay be nil ✓ IsAtLeastduplication eliminated —version.ProjectVersionis now a type alias fortypes.ProjectVersion; methods live inmdl/typesonce, used everywhere ✓convertProjectVersioneliminated — no longer needed since it's the same type ✓
One remaining issue
IsSupported / SupportsFeature method-to-function conversion is an undocumented public API break
The PR converts:
// before (methods)
pv.IsSupported()
pv.SupportsFeature(feature)
// after (package functions)
version.IsSupported(pv)
version.SupportsFeature(pv, feature)A grep shows no callers outside version.go itself, so this is safe for internal code. But these are exported symbols — if anyone imports sdk/mpr/version externally, their code breaks silently. Worth a CHANGELOG entry under Changed (analogous to the mdl/types extract entry in v0.7.0).
Minor observations
MockBackenddoesn't havevar _ linter.LintReader = (*MockBackend)(nil)— not a safety gap sinceMockBackendalready hasvar _ backend.FullBackendandFullBackendis a superset ofLintReader. But adding it would make the mock's linting capability explicit and keep the two assertions in sync.mdl/linternow importssdk/microflows,sdk/pages,sdk/securityfor the interface method signatures. This is unavoidable and correct — no new cycles introduced.
Overall: Approve. The type-alias approach for ProjectVersion is particularly clean. The IsSupported/SupportsFeature change is the only thing worth noting before merge, and only as a changelog entry since there are no internal callers to update.
Why
The linter and executor packages import
sdk/mprdirectly for*mpr.Reader, coupling them to the concrete SQLite implementation. This blocks the WASM build (PR #7 series) wheresdk/mpris unavailable, and prevents testing with mock backends. Introducing aLintReaderinterface breaks this dependency using Go's implicit interface satisfaction — zero changes needed in the 10+ linter rule files.Summary
LintReaderinterface inmdl/linter/context.gowith 7 methods matching both*mpr.ReaderandMprBackendsignatures*mpr.Readerdependency withLintReaderinterface throughout linter and executor packagesReader()methods andreaderProviderinterface from executor — callers now passBackend()directlysdk/mprimports remain inmdl/linter/andmdl/executor/production codeTest
make build && make test && make lint-go— all pass