MGS3005: tool too old
The binary exists, reports a version, and is usable - and that version is below the minimum declared for it.
[MGS3005] golangci-lint v1.9.0 is older than the supported range for
this project (min 2.0)
Its other side is MGS3006, for a version at or above a ceiling.
The fourth question about a tool
Each is distinct, and only the last two used to have answers:
| question | declared by | code |
|---|---|---|
| does it exist? | nothing - PATH lookup | MGS3003 |
| what version? | Tool.probe |
- |
| is it usable now? | Tool.ready |
MGS3004 |
| is it in the supported window? | Tool.supported |
MGS3005, MGS3006 |
Without a minimum, a too-old binary fails with whatever it says about an unrecognized flag - the same misleading failure MGS3004 exists to prevent, one question over.
Declaring one
export fun mgs_getTools() > {str: Tool} {
return {
"golangci-lint": Tool{
probe = Command{bin = "golangci-lint", args = ["--version"]},
supported = VersionBounds{min = "2.0"},
},
};
}
min is an inclusive floor and below is an exclusive ceiling. Both are plain
versions, not a constraint range: a range language puts a syntax between the
author and the two cases that matter, where the comma means AND, || means OR,
and ^ and ~ mean materially different things in different ecosystems.
A partial version is read as you would say it out loud. min = "1.21" means
1.21.0, so 1.21.4 satisfies it.
A malformed bound is a load error rather than a silent pass - a window nobody can parse protects nobody.
A window needs a probe: there is nothing to compare without one.
Two owners, intersected
Tool.supported states what the spell's ops need to function: "go::test does
not work below 1.21." A project states its own policy in its magusfile, under
magus\project's tools key:
magus\project({
"spells": [go],
"tools": { "go": { "min": "1.26" } },
});
The two are intersected, narrower bound winning on each side, so neither can loosen the other. A spell cannot relax a version a repo has not qualified, and a repo cannot ask an op to run on a version it is known to break on.
The policy side is declared per project, not in magus.yaml, and that is not a
filing preference. Config load merges a user-global tier beneath the workspace,
so a bound written there could be set in one person's private file and silently
gate every workspace on their machine. A magusfile is committed and is read by
everyone who reads the project, and being per project it also lets console and
docs hold different windows instead of sharing one workspace-wide map. Sharing
a window is an explicit import of a shared module, never inheritance by position
in the tree. magus.yaml has no tools key; a block written there is inert and
reported only as an unknown-key warning. See
workspace.
Resolution
Upgrade the binary to the floor or above, through whatever installs it for this repo - a version manager, the toolchain's own installer, the CI setup step.
If the floor is the wrong bound rather than the binary, edit the declaration that
set it: Tool.supported in the spell, or tools in the project's own
magus\project call. Which one is binding is not in the message, since the two
are intersected before the comparison. magus describe tools prints the
installed version, both declarations, and the effective window, so read that
before editing either.
What it deliberately does not do
An unprobeable tool is not "too old." If the probe fails, the window passes. Failing there would make a bound a second way for a missing tool to break, with a worse message than MGS3003's.
Output magus cannot parse is not "too old" either. A tool whose version line carries no semver is left alone rather than judged against a window it was never going to satisfy.
The check runs once at the start of a run, over every project it touches, before any target executes. It reuses the version probes magus already performs to key the cache, so a declared window costs no extra forks.
It deliberately does NOT hang off op dispatch. Enforcement follows the DECLARATION - a window is stated per project - not the mechanism a target happens to use. A project whose targets shell out directly instead of dispatching a spell op is held to its window exactly like one that does; an earlier version checked at dispatch and silently gated nothing for those projects.