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

MGS1033: a cacheable target composes an op that reaches outside the tree

A target composes a spell op that declares a relation to the world outside this tree, and is still cacheable:

[fail] cacheable external ops: 1 cacheable target(s) compose an op whose inputs or
effects the cache key cannot see, so a replay reports a verdict that has expired or a
side effect that never happened
    .: target "image-scan" composes docker::trivy-image (magusfile.buzz): the op's
    verdict comes from data outside this tree that no probe identifies; declare
    skip_cache with a reason, or declare an observe probe on tool trivy in the spell so
    the data's identity keys the cache

The two shapes

An op declares one of two values, and which one you have decides the fix.

mutates-external is an effect that lands somewhere else: a registry push, a signature, a deploy. A replay does not perform it. The target reports success and the artifact is not there, which surfaces far downstream in whatever assumed it was.

reads-external is a verdict that comes from a live feed: a vulnerability database, an advisory index, a signature checked against a transparency log. A replay reports what the feed said when the target last ran. The tree is unchanged, so the key is unchanged, so the answer keeps being green after it stopped being true. That is the whole reason this is a check rather than something you would notice: a stale scan looks exactly like a clean one.

Why the target and not the op decides

magus does not make a target uncacheable on an op's say-so. An op knows what it reaches; only the target composing it knows whether that reach determines the result. A target that scans an image is a different case from one that merely lists what a registry holds before doing something local with it.

So the op declares the fact and the target answers for it - the same division MGS1026 draws for a credential.

Resolve it

For mutates-external, one answer. Declare the target uncacheable, with the reason:

magus\project({
    "targets": {
        "image-push": {"skip_cache": "pushes a registry digest per invocation; a replay would publish nothing"},
    },
})

For reads-external, skip_cache works too, and it is usually the worse trade: it forfeits caching forever to avoid a staleness that only matters when the feed actually moved. The better answer is to put the feed's identity IN the key, so the target stays cacheable and a hit can only replay a run made against the same data. That is a declaration on the spell, beside the tool's version probe:

export fun mgs_getTools() > {str: Tool} {
    return {
        "trivy": Tool{
            probe = Command{bin = "trivy", args = ["version"]},
            key = VersionKey{upTo = VersionComponent.patch},
            observe = Command{bin = "trivy", args = ["version", "--format", "json"]},
        },
    };
}

The probe's output lands in the key as an obs: line. See Cache for the input class it joins, and Charms for the update charm that refreshes the data on purpose rather than by accident.

An observation probe only pays for itself when the tool can also run OFFLINE. A scanner that silently refreshes its own database on every run changes its verdict under an unchanged key, and observing it then records the copy it held a moment before it replaced it.

When to ignore it

Reaching the network is not the same as being external, and the check only sees what an op declares. go mod tidy contacts a module proxy and declares nothing, because go.sum pins what comes back: with the tree unchanged the answer is unchanged, and a new import is a tree change. If an op in your own spell is marked and should not be, the fix is the spell's declaration rather than a skip_cache on every target that composes it.

What this check cannot see

It reads the target body statically, so it finds ops the walk can attribute to a target - the same list magus describe target prints under spells. An op reached through a helper the walk cannot follow is invisible to it, like an unreached ctx.readsFiles (MGS1004). It under-reports rather than over-reports.

It also cannot see a raw proc\exec of the same binary. That is a real gap and it argues for the op: a spell op declares what the raw exec only does.

See also

  • Cache - stated and probed observations, one input class
  • Charms - the update charm, and why it is not rw
  • MGS1026 - the sibling check for a cacheable target reading a credential
MGS1033magusfilecacheskip_cachescannersobservationsexternal
Last updated (0c2c811f)
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.

Charm

An execution modifier attached with : (lint:rw) that changes how a target runs, not which one; the built-in rw flips a check-only target to mutate in place, and ci always strips it. See charms.

Module

A magus stdlib namespace a magusfile imports for host capabilities: filesystem, exec, vcs, and more. See the module reference.

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.

Invocation

One magus process's recorded facts - the targets it finished, their outcomes, the lease it acted as, and the session it ran in when a host delivered one - kept in a repo-scoped store every worktree shares. magus session lists them; the store prunes itself by last-fact age.

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.

Advisor

One read-only check from the advice suite: it reads the changeset through magus and writes one titled section of findings. The same advisors run as a pull request comment in CI and inside magus diff --impact locally.

Conventions

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