MGS3032: an unordered share of one claimable file
magus job fork, the client tool (magus\job.put) and a magusfile's magus\job.put refused a job because
one of its write paths and another live job's write path, held in a different
checkout, name the same file, and nothing orders the two jobs against each other:
[MGS3032] job: wave/b declares "magusfile.buzz", and wave/a (running, updated
4m3s ago) already holds "magusfile.buzz". Both are live and neither is the
other's parent, child, or depends_on partner, so magusfile.buzz has one
owner unless the rows say otherwise. wave/a has touched none yet in it so
far. Claim `magusfile.buzz#<declaration>` on both rows, add `--depends-on
wave/a` to wave/b, or fold wave/b into wave/a
Nothing was written to the job store.
Why
A whole-file write path claims the whole file, and two whole-file leases on
one file both read "mine wins": neither is ever refused, which is what makes
the whole-file spelling look free. It is not free once the file is genuinely
shared between two agents working at once, and the plan has three ways to say
so: a declaration claim, a depends_on edge, or one job instead of two. This
refusal is the fork-time cost that makes naming one of those the cheaper
choice. It fires only when magus can prove the fact: the two write paths name
one literal file (no glob, no directory, no project root), that file has a
diff driver so a declaration claim on it could actually be graded, and
neither row's parent chain nor depends_on already sequences the pair.
What is refused
- Two whole-file entries on one file, held live in two different
checkouts, with neither job the other's parent, child, or
depends_onpredecessor. - A whole-file entry against a claim on that file:
run.gohere andrun.go#executeStagesthere still collide, since the whole-file entry covers every declaration.
What is not refused
- A glob, a directory, or a project root. Only a single literal file is judged here; the overlap report and MGS3018 cover the rest.
- A file with no diff driver (
.txt,.json,.yamlunless you have registered one). Nothing can grade a declaration of it, so the pair is recorded, not refused;depends_onis the only move. - Two claims on different declarations of one file (
run.go#Aandrun.go#B): that is exactly what a claim is for. - A pair already ordered: a parent and its child, or two jobs joined by
depends_on, directly or through any number of hops, in either direction. - Two rows held in the same checkout, or a row not yet taken into one
(
CheckoutRootempty). That is whatRefuseSharedCheckoutandwrite_proof: overlappingalready watch; sharing a file there stays an advisory the orchestrator that declared both rows can act on. - A holder blocked on
depends_on. A blocked row owns none of its write paths yet, so it cannot collide today; the fork that unblocks it runs this same check again, against whatever holds the file then. - A holder whose job has exited. Its holder returned, so its editing is
over; the row stays until
job waitgrades it or its work lands on the base, but it holds no file against a new fork.
What to do
Claim a declaration on both rows, so the two jobs partition the file instead of racing on it:
magus job fork wave/a --write-paths magusfile.buzz#lint_rules
magus job fork wave/b --write-paths magusfile.buzz#lint
Or sequence them:
magus job fork wave/b --write-paths magusfile.buzz --depends-on wave/a
Or fold the work into the job that already holds the file.