magus v0.4.3 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
3 min read

MGS1036: a narrowed footprint dropped the files its own ops read

A target declares its own footprint with ctx.readsFiles(...) and then calls a spell op that reads a kind of file the footprint never names. Editing those files does not move the key, so the op replays rather than runs.

[fail] footprint-drops-op-globs: 1 target(s) replace their cache footprint and then run an
       op over files that footprint never names, so an edit to those files replays the op
       instead of running it
  libs/testlayout: format declares its own footprint and calls go[go-fmt], whose spell
  reads **/*.go **/*.txtar **/*.s **/*.S **/*.c **/*.h go.mod go.sum go.work go.work.sum;
  no declared glob names any of them

Why this matters

ctx.readsFiles is an ownership boundary, not an extra margin. The moment a target declares one, buildStep resets that target's sources to the magusfiles and folds back only the refs the body declared plus the spell sources specific to that target. The project-wide baseline is gone, and so is every glob a spell contributes project-wide.

Most spells contribute project-wide. The go spell's mgs_listRequiredGlobs takes no target argument, so every glob it has is project-wide, and a target that narrows its footprint keeps none of them unless it says so.

What makes this worth a code rather than a comment is that the failure is green. The op is skipped, not failed. A sibling target that kept the baseline still re-runs, so a ci composing both passes while the formatter or the suite never saw the change:

  • A format narrowed to **/*.md still calls go-fmt. A pure-Go edit replays it, so nothing is reformatted, and lint, build and test all re-run and pass.
  • A test narrowed to four console files drops **/*.go. Measured in this repository: about 8,400 new lines of *_test.go landed and magus run test . replayed the cached verdict, coverage profile untouched, until --no-cache.

What it does not report

Narrowing on purpose. A footprint naming one *.go path has said what it reads; the check cannot know whether that path is the right one, and does not guess. It fires only when the footprint names no file of any kind the spell reads, which is the difference between a claim an author made and one they forgot.

ctx.modifiesExistingFiles. Those refs fold into the target's sources exactly as inputs do, so a target that declares what it edits has keyed those files. That is the correct declaration for gofmt and dprint, which amend authored files rather than producing them, and reporting it would be reporting the fix.

A target with no footprint of its own. Without ctx.readsFiles the project baseline keys the target and there is nothing to drop.

A spell that declares globs per target. Those survive the reset (WithTargetSources), so such a spell loses nothing.

Resolve it

Name what the ops read, in the declaration that matches the relationship:

export fun format(ctx: magus\Context, args: [str]) > void {
    ctx.readsFiles("dprint.json");
    // gofmt and dprint AMEND authored files rather than producing them, so these are
    // modifiesExistingFiles: the bytes key the target without joining the snapshot.
    ctx.modifiesExistingFiles("**/*.md", "**/*.go", "go.mod", "go.sum");
    go["go-fmt"](ctx);
    markdown["dprint"](ctx);
}

Declaring the files project-wide does not fix it. A project-wide sources entry reaches only the targets that kept the baseline, which are the ones that do not read the file; the target that does read it replaced its footprint and never sees the entry. That is also why MGS1028 can go quiet on a declaration that keys nothing: its check unions project sources with per-target refs, and a file declared in the wrong half still counts as declared.

If the target genuinely does not read those files, the op does not belong in it.

See also

  • Cache model - what a per-target footprint replaces, and the rule for where a glob belongs
  • MGS1004 - a footprint declaration the loader never reaches
  • MGS1028 - a changed file that seeds a project it does not key
  • MGS1029 - a source glob rooted inside a pruned directory
  • MGS1034 - a target reading a file it declares as its own output
MGS1036magusfilesourcescachereadsFilesfootprintdoctor
Last updated (d966bfce)
Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Snapshot

A point-in-time view of live state - the pool's occupancy or a tick of exported metrics - as opposed to accumulated history. See server.

Ownership

An insight lens: author concentration - the primary author and their share, the distinct-author count (the bus factor), and abandonment. See insight.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate.

Conventions

This page uses none of the site's convention markers. The full set is on the conventions page.