Files
troupe-standup/wizard-work/null/deporder/README.md
T

90 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# deporder — deterministic build-order planner for task DAGs
Lane: Codeforge (hard coding / agentic reasoning). Author: null.
Location of record: `/root/.hermes/wizard-outputs/null/deporder/`.
## What it is
A stdlib-only Python module + CLI that turns a task/dependency spec (JSON) into
a build plan:
* `topological_order` — serial order, lexicographically smallest ready task first
* `waves` — maximal parallelism batches (wave k = longest dependency chain has k edges)
* `critical_path` — weighted longest path (the makespan floor), with a unique,
lexicographically canonical path
* `schedule` — greedy list scheduling on N identical workers, with precedence and
capacity guarantees
* `find_cycle` — reports one cycle as a real path `x -> y -> ... -> x`
Every ordering decision is deterministic: ties break on lexicographically smallest
name, so two runs over the same spec — and runs over specs whose keys were inserted
in a different order — produce byte-identical output.
## CLI
```
python3 deporder.py plan examples/pipeline.json [--workers N]
```
Exit codes: `0` ok · `2` dependency cycle · `3` unknown dependency · `4` malformed spec.
Success prints the plan as JSON on stdout; failures print one diagnostic line on
stderr and nothing on stdout.
## Run the tests
```
python3 test_deporder.py # 42 tests, stdlib unittest, no pytest needed
python3 mutate_teeth.py # mutation harness: do the tests bite?
```
## Evidence (measured on this box, Python 3.14.7)
| Claim | How measured | Result |
|---|---|---|
| 42 tests pass | `python3 test_deporder.py` | `Ran 42 tests in 1.902s — OK` (exit 0) |
| Differential vs naive reference | 200 random DAGs × (order, waves, critical path) compared against O(V·E) fixpoint / O(V²) reference implementations inside the suite | all equal |
| Schedule invariants | 150 random DAGs × 1–4 workers: each task placed once, precedence `end[d] ≤ start[n]`, ≤ N tasks in flight (event sweep), per-worker non-overlap, `makespan ≥ critical path` | all hold |
| 1 worker == serial | `makespan == Σest` | exact |
| ≥N workers == critical path | `makespan == critical_path` | exact |
| Scale | 20 000 nodes / 39 987 edges: order + waves + critical path + 8-worker schedule | **0.92 s** (budget in-suite: 10 s) |
| Determinism | shuffled spec key insertion order → identical `json.dumps(sort_keys=True)` | identical |
| Tests have teeth | `python3 mutate_teeth.py`: 11 literal source mutations | **10/10 required mutants caught**, 1 documented equivalent, harness exit 0 |
| CLI contract | exit 0 on `examples/pipeline.json`; exit 2 + `dependency cycle: draft -> review -> publish -> draft`; exit 3 + `unknown dependencies: app -> missing_module` | as specified |
### Bugs this suite actually caught while building it
All three were found by running things, not by reading the code:
1. **Scheduler stall (infinite loop).** The clock-advance step took
`min(free_time, completion_time)` even when a worker was already free at `now`
with nothing ready — `now` stopped moving and `schedule()` never returned.
Caught by `PlanTests` hanging. Fixed by advancing only to strictly-future
events and raising `RuntimeError` on stall (unreachable for a DAG).
2. **O(V²) critical path.** `min(n for n in dist if dist[n] == max(dist.values()))`
recomputed the max inside the generator: a 20k-node plan took **15.98 s**.
Hoisting `max()` gave **0.85 s** (≈19× faster) with identical results.
3. **Dead branch found by the mutation harness.** The first version of the fix
above also consulted future worker-release times when choosing the next event.
The mutation that deleted that branch *survived* — it was unreachable, since
workers are only ever released by completions already drained at `now`. The
branch was removed rather than left as decoration.
## Mutation harness (`mutate_teeth.py`)
Each mutation is a literal source edit applied to a copy of `deporder.py` in a
temp dir; the mutation is *caught* if the suite exits non-zero on the mutant.
Mutations tagged `equivalent` are expected to survive — they document a
genuinely redundant guard rather than a hole in the tests.
See the harness output in `EVIDENCE.txt` (verbatim run log).
## Files
```
deporder.py implementation (module + CLI)
test_deporder.py 42-test stdlib suite (unit + differential + CLI + perf)
mutate_teeth.py mutation harness
examples/pipeline.json worked example (makespan 14.0 vs 18.0 serial, 3 workers)
EVIDENCE.txt verbatim transcripts of the runs quoted above
```