You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: template/PCDA/quality-cycle/09-parallel-lanes.md
+16-8Lines changed: 16 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -52,16 +52,24 @@ The first defense against integration tangling is to not create it. Assign work
52
52
53
53
Partitioning by issue id alone is not enough — it isolates the *runs* but not the *changes*. The information needed is already produced at Plan: root-cause analysis names the files / area a fix will touch. Lane assignment is therefore a **Plan-beat judgment** — the same place the human decides scope and which issues to brief ([03 - Cycle Automation](03-cycle-automation.md)) — not a mechanical sharding step. When the touched areas genuinely cannot be predicted, prefer fewer, broader lanes and lean on the integration check below.
Manual wave-splitting (run a prerequisite batch to COMPLETE, *then* the next) enforces ordering by hand; it does not scale to a batch with a real dependency graph, which is exactly when the lane pool is most useful. A brief may instead **declare** its ordering constraints and let the scheduler enforce them:
57
+
Manual wave-splitting (run a prerequisite batch to COMPLETE, *then* the next) enforces ordering by hand; it does not scale to a batch with a real dependency graph. A brief instead **declares** its ordering constraints and the scheduler computes the order: a batch handed to `pdca flow` runs as an ordered sequence of **dependency waves**(`waves.compute_waves`). Each wave holds only mutually-independent work; its bundles build in parallel, are signed off and published, and then the wave's accepted patches are **folded onto a run-scoped integration branch the next wave builds on** — so a dependent builds on its prerequisite's accepted result *within one run*, with no merge by the harness.
58
58
59
-
-**`- **Depends on:** <id>[, <id>…]`** — a topological gate. The in-driver pool dispatches a bundle only once every declared prerequisite is **COMPLETE** (signed off, not merely built). Because a prereq reaches COMPLETE only after its sign-off in an earlier pass, a dependent waits across passes — exactly the manual wave plan, now machine-enforced.
60
-
-**`- **Depends on (merged):** <id>[, <id>…]`** — a *stricter* gate for a dependent that **edits files a prerequisite also edits**. `Depends on` waits only for COMPLETE — which means "a draft PR was opened", **not merged**. A dependent's Do runs in a worktree off the target base (`origin/<base>`), so a prereq whose PR is still open is *absent* from that base: the dependent is built without the predecessor's diff and its PR conflicts at merge. This field holds the dependent until the prereq's PR is **merged into the base** (read from the prereq's recorded PR via `gh pr view`), so Do genuinely builds on the merged result. Because the flow only *opens* draft PRs (it never merges), a merge-gated dependent is **held across `pdca flow` runs**: its prereq is published in one run, a human merges that PR, and a later `pdca flow <dependent>` finds it merged and proceeds. Best-effort and fail-closed: anything not confirmable as merged keeps the dependent safely blocked.
61
-
- **`- **Stacks on:** <id>[, <id>…]`** — auto-stacked chains (issue #123). Like `Depends on (merged)` it is for a dependent that **edits files a prerequisite also edits**, but instead of *waiting for the prereq to merge* (multi-run), it builds the dependent on the prereq's **just-produced branch** within the **same `flow` run** and publishes a **separate stacked PR** (`gh pr create --base <prereq-branch>`, one PR per item showing only that item's increment). A stacked dependent is eligible once its prereq is **COMPLETE with a published branch**; its Do worktree bases off `origin/<prereq-branch>` and its PR targets that branch — the base is **derived from the prereq's `publish.json`**, never written in the brief (it doesn't exist at Plan time). So a planned, file-overlapping refactor sequence (`203 → 207 → 204`) completes as a reviewable PR stack in **one invocation** — independents still run in parallel; a `Stacks on` chain is sequential within itself. **Merge the stack bottom-up with merge-commit / rebase-merge, not squash** (a squash drops the parent's commits, so a child retargeted to the base re-shows the parent's diff until rebased); publish warns if the target repo disallows merge commits. If a parent's branch later changes (e.g. a squash-merge between runs), rebuild the items stacked above it (`signoff --iterate-do`) on the new foundation. Use `Depends on (merged)` instead when you'd rather wait for each PR to merge before the next builds.
62
-
-**`- **Conflicts with:** <id>[, <id>…]`** — a same-wave exclusion. Two bundles that touch a shared resource (e.g. both edit one `ci.yml`) are **never in flight in the same concurrent wave**; the pool serializes them across lanes while still parallelizing everything else.
59
+
-**`- **Depends on:** <id>[, <id>…]`** — the topological edge. The dependent lands in a **later wave** than every prerequisite and builds on the integration branch that carries the prereqs' accepted diffs. Because the fold carries the diff forward without a merge, this single field now **subsumes** the former `Depends on (merged)` and `Stacks on`.
60
+
-**`- **Conflicts with:** <id>[, <id>…]`** — an undirected "edit a shared resource" relation. Because each wave is folded onto the base before the next builds, two conflicting bundles must not share a wave; the leveler **orients each conflict pair into different waves** (by a deterministic id order, unless a dependency path already separates them), so the later one rebuilds on the earlier's folded result instead of colliding.
61
+
-**`- **Depends on (merged):** / **Stacks on:**`** — *deprecated, still parsed.* Both fold into a plain `Depends on` edge: the wave model gives the dependent the prerequisite's accepted diff (via the integration branch) without the old cross-run merge wait (`Depends on (merged)`) or the single-chain branch-stacking (`Stacks on`, whose multi-parent gap the integration branch fixes). Author `Depends on`.
63
62
64
-
The fields are **additive and backwards-compatible**: with none declared, every bundle is always eligible and dispatch is byte-for-byte the prior **sort-by-name pool**. An unschedulable graph — a cycle, or a dependency that is neither in the batch nor an already-COMPLETE bundle — is a **hard error rejected before any build** (`pdca flow` aborts up front). `pdca status` shows a `[blocked-by: <ids>]` flag so the queue reads as a DAG, not a flat list. Declared ordering complements lane planning: planning *avoids* integration tangling by code locality; `depends_on` / `conflicts_with`*enforce* the residual ordering that locality cannot express.
63
+
With no fields declared the batch is a **single wave**, dispatched byte-for-byte the prior **sort-by-name pool**. An unschedulable graph — a cycle, or a dependency neither in the batch nor an already-COMPLETE bundle — is a **hard error rejected before any build** (`waves.compute_waves` raises; `pdca flow` aborts up front). `pdca status` shows a `[blocked-by: <ids>]` flag, and `pdca waves` prints the computed wave plan up front. A post-Do **overlap audit** flags two same-wave bundles whose patches touch a shared file but declared no `Conflicts with` — a likely planner omission.
64
+
65
+
### Carrying a wave forward — stack (default) vs merge [built]
66
+
67
+
How a wave's accepted work reaches the next wave's base is `[driver].wave_mode`:
68
+
69
+
-**`stack`** (default) — fold the accepted patches onto a run-scoped integration branch on `origin` (push-only, so a **fork** contributor can do it) and open each dependent's PR as a **stacked PR** against it. The harness never merges; you merge the PR stack bottom-up with a **merge commit** (not squash, which would drop a parent's commits). A non-applying patch (an undeclared overlap) STOPs the run. STOP discipline holds throughout.
70
+
-**`merge`** (own-repo / continuous-delivery only) — `gh pr merge` each non-final wave's PRs so the next wave builds on the genuinely-merged base. Needs **merge rights** on the base remote (a fork lacks them upstream — keep `stack` there) and relaxes STOP discipline; fail-closed (a non-mergeable PR STOPs the run).
71
+
72
+
Optionally (`[driver].regate_between_waves`), the repo-scoped gates re-run over the folded integration tip before the next wave builds on it, so a combination that is red though each fix was green *alone* STOPs the run.
65
73
66
74
## Integration validation — at the merge boundary
67
75
@@ -78,7 +86,7 @@ The lanes parallelize the **unattended** band only. The three human touch points
78
86
-**Check sign-off** is interactive — converge here. (A *single-workspace* run can batch sign-off across the fanned-out bundles into one cheap-first session; independent lane *copies* each carry their own sign-off queue, so the human attends them in turn — an ergonomic cost of full copies versus an in-driver fan-out.)
79
87
-**Act** runs once, across the completed cycles of all lanes — serial by nature.
80
88
81
-
So the shape is:**Plan (serial) → Do + Check fan out across lanes → sign-off (serial join) → publish → integration re-gate at the merge boundary → Act once.** Parallelism lives entirely in the unattended middle; planning and the merge re-gate carry correctness across the results.
89
+
So the shape is,**per wave**: Plan (serial) → Do + Check fan out across lanes → sign-off (serial join) → publish → **fold onto the integration branch** the next wave builds on (or, opt-in `merge` mode, `gh pr merge`) → optional re-gate — repeated wave by wave, then **Act once**across the batch. Parallelism lives in the unattended middle of each wave; the wave ordering and the integration fold (then the human's bottom-up merge of the PR stack) carry correctness across the results.
82
90
83
91
## Two realizations — separate workspaces vs an in-driver pool
0 commit comments