Build planner
Converts an analysis result into an executable plan.
Terminology
The planner produces a statically derived execution plan, also referred to as a dependency-aware build plan or a derived schedule.
It does not produce a “minimum number of passes”. Minimality is not established — see non-guarantee N4 in STATIC-ANALYSIS.md. Where a bound is derived it is model-minimal: the shortest the model supports, which may exceed the true requirement. Use that phrase, or none.
Plan representation
@dataclass(frozen=True)
class Step:
tool: str # "pdflatex" | "bibtex" | "makeindex" | "spellcheck"
args: list[str]
jobname: str
reason: str # traceable to a fact or to the classification
verify_after: bool
@dataclass(frozen=True)
class BuildPlan:
classification: Literal["CERTIFIED", "BENIGN_CYCLIC", "AMPLIFYING", "UNKNOWN"]
predicted_runs: int | None
steps: list[Step]
max_runs: int # hard cap for the executor
stop_condition: Literal["planned_count", "aux_fixpoint", "cap"]
diagnostics: list[Diagnostic]
arg_stats: dict # |V|, |E|, SCC count, analysis_ms
model_version: str
BuildPlan is the sole interface between the compiler and the runtime, and it is serialisable JSON. Everything the executor needs is in it; the executor never re-runs the analysis.
reason on every step is required. A plan whose steps cannot be justified is not reviewable, and auxflow plan --explain is one of the project’s main demonstration artifacts.
Lowering rules
Applied in order.
- Auxiliary per-file tooling — emit spell-check or lint steps only over files marked reachable in the inclusion graph. Unreachable files cannot affect the output and need not be scanned. (Dead-file elimination.)
- Bibliography tool — emit it if and only if citation facts exist and a bibliography command is present. Place it after the first engine run, which is when its input exists. (Dead-tool elimination.)
- Index tool — emit if and only if index facts exist. Optional; only if the construct is implemented.
- Engine runs — emit
predicted_runssteps when the document is certified. Otherwise emit the conservative default. - Verification — set
verify_afteron the final engine step. Always, in every classification, including certified. - Amplifying refusal — emit no steps; emit the diagnosis; exit non-zero unless
--force.
Spell-check is optional and disabled by default. When enabled, it scans only files reachable from the entry file. A certified plan uses at least its predicted count as max_runs; the default cap is six.
Safety invariant
The emitted plan is never shorter than the conservative default unless the classification is
CERTIFIED.
Implemented as an assertion in the planner, written before any other planner code, and enforced by a property test over randomly generated classifications. It is the reason an analysis bug cannot silently corrupt a document: the worst outcome is a plan that is too long.
The conservative floor counts three engine runs. Spell-check, bibliography, and index steps do not replace engine runs.
Conservative default plan
[spellcheck over reachable files]
pdflatex
[bibtex]
pdflatex
pdflatex
verify; extend while the state is still changing, up to max_runs
Chosen to match conventional practice so that a non-certified document is never worse off than it would have been without AuxFlow.
Stages, ordering and dependencies
The plan is a linear sequence, not a DAG, for the ordinary single-document case. Ordering constraints:
- The bibliography tool must follow the run that produced its input and precede the run that consumes its output.
- Every engine run depends on the auxiliary state left by the previous step.
- Per-file auxiliary tooling has no ordering constraint against engine runs and is placed first so that a failure surfaces before expensive work.
Parallelism opportunities
Recorded for completeness; none are implemented in the MVP.
- Per-file auxiliary tooling is embarrassingly parallel across reachable files.
- Independent documents are trivially parallel; this is the basis of the stretch work in FUTURE-INFRASTRUCTURE.md.
- Engine runs of a single document are inherently sequential — each consumes the previous run’s output. No amount of analysis changes this, and claiming otherwise would be wrong.
Stopping conditions
| Condition | Trigger | Action |
|---|---|---|
planned_count | planned runs completed and verification passes | success |
aux_fixpoint | normalised auxiliary state unchanged between runs | success |
extend | planned runs completed but state still changing | perform one more run, up to max_runs; record a prediction miss |
cap | max_runs reached with state still changing | exit non-zero; report suspected non-convergence |
Prediction misses are logged rather than hidden. They are a headline evaluation metric and the empirical test of assumption A2.
Failure states
| State | Cause | Behaviour |
|---|---|---|
tool_error | non-zero exit from an external tool | abort; surface the tool log unmodified |
missing_input | a file named in the plan does not exist | abort before executing anything |
cap_exceeded | see above | abort; emit the suspected-non-convergence report |
opaque_abort | --strict and classification is UNKNOWN | refuse to execute |
amplifying_refused | classification is amplifying, no --force | refuse; emit the diagnosis |
Engine logs are never rewritten or filtered. Reformatting an engine error is a good way to hide the cause of a failure.
Example
A certified document with a bibliography, derived bound 4:
{
"classification": "CERTIFIED",
"predicted_runs": 4,
"max_runs": 6,
"stop_condition": "planned_count",
"model_version": "arg-0.1",
"arg_stats": { "V": 148, "E": 331, "sccs": 0, "analysis_ms": 41 },
"steps": [
{ "tool": "pdflatex", "args": [], "jobname": "main",
"reason": "run 1: emit LABEL_*, TOC_ENTRY, CITE_USE facts",
"verify_after": false },
{ "tool": "bibtex", "args": [], "jobname": "main",
"reason": "12 CITE_USE facts and \\bibliography at main.tex:214",
"verify_after": false },
{ "tool": "pdflatex", "args": [], "jobname": "main",
"reason": "run 2: BBL.exists (stable_pass 1) consumed",
"verify_after": false },
{ "tool": "pdflatex", "args": [], "jobname": "main",
"reason": "run 3: LIST_EXTENT(toc) (stable_pass 3) after the bibliography entry was added",
"verify_after": false },
{ "tool": "pdflatex", "args": [], "jobname": "main",
"reason": "run 4: PAGEREF_USE consumes LABEL_PAGE; P=3 so runs=P+1",
"verify_after": true }
],
"diagnostics": []
}
An amplifying document produces "steps": [], a populated diagnostics array, and a non-zero exit.