ADR-052: Several packs per run¶
Status¶
Accepted; amended by ADR-053.
Decision¶
A run may use several packs. The single-pack pipeline stays as it is and runs once per pack, in
id order, over what the packs before it wrote; one gate, journal, write, verification, report and
undo serve all of them.
D1. Packs come from the wheel, the user's directories and --pack¶
pack_dirs in ~/.config/obelize/config.yml adds directories laid out like the wheel's
(<provider>/<slug>/pack.yaml); --pack <path> names a file. A repository's .obelize.yml may not
name a directory, for the reason it may not name a model: a pack decides what is written and which
imports a model may add (B1, TM-1). An id in two places is refused (loader.known), and a pack
loaded by id must carry that id.
D2. Without --pack, every pack the repository uses runs¶
Every known pack is scanned over one read; one with a finding that is not not_a_usage is chosen.
--pack is repeatable and names exactly the packs that run. None chosen is not a failure: the run
says which packs it checked, writes a run folder with packs: [], and exits 0.
D3. Two packs that could feed each other are refused¶
loader.conflict: legacy modules that overlap, one pack writing a module the other migrates, or one
distribution named by both (from, to or transitive). Without those neither can see the other's
output through a module, so the order between packs breaks ties and decides nothing else. Exit 2.
D4. Each pack's turn is the single-pack run¶
A pack is scanned again only when an earlier one wrote a file it has a finding in, since the lines
have moved. File atomicity (F-1) and the manifest verdict (F-2) are each pack's own: one pack can
leave a file as it was while another writes it. codemod.chained joins the turns: a file keeps
the first before and the last after, an edit's rule_id is <pack id>:<rule id>, and its
line is in the file as its pack saw it. setup.py is read as a source and as a manifest, so the
passes of one or several packs can return it twice; chained joins every return of a path into one
outcome from its first bytes to its last, whichever list a pass put it in.
D5. A blocked pack is withheld alone¶
A pack the repository rules out (ADR-050 D4, D5) has every eligible row withheld for its
reason by codemod.blocked and plans nothing; the other packs run. packs[].blocked in run.json
carries the reason, and the withheld rows make the exit 4, so a run that wrote for one pack and
stopped for another does not read as finished.
D6. A model is asked about one pack¶
The guard authorises the imports of one pack's targets (TM-4), so --model and --accept-model
with several packs exit 2, as does --show-context; a model only the user's file configures is skipped, with a line on stderr. A pack that is its
own target (ADR-053 D13) is never asked about either, since
a proposal is one row and the pack writes a whole repository or none: --model, --accept-model
and --show-context with one exit 2, and a configured model is skipped with a line on stderr.
D7. The evidence says which packs¶
findings.json, plan.json and run.json carry packs[], ordered by id; run.json's
blocked moves into each row. A pack's bytes and hash are copied to
packs/<provider>/<slug>/. A pack id has no dot, so the path cannot leave the folder.
D8. The repository is read once¶
runner.read walks and reads every selected path once into a Tree, keeping the bytes only where a
pack's prefilter token or a module only the legacy distribution installs occurs. Every read of a
scan goes through Tree.contents, and fix no longer reads the files a second time between the
scan and the plan.
Consequences¶
- Every document and the run folder's layout change; CHANGELOG lists it (ADR-048).
- A pack scanned again after an earlier pack wrote costs a second pass over its own candidates;
bench/crossover.pyis where to measure it if a repository makes it matter. - Two packs sharing a target distribution are refused together until a pack needs it.