# Parallelism wave scheduler

`Compute-WaveSchedule.ps1` is a deterministic graph-coloring scheduler that
computes a dependency-ordered wave schedule of disjoint work items (one span per
workflow sweep). It ensures no two sweeps in the same wave touch the same module,
add the same contract type, or touch SharedKernel.

## What it does

Given a list of span records it:

1. Assigns a dependency layer to each span (topological sort over `dependsOn`).
2. Computes each span's conflict degree (how many other spans it conflicts with).
3. Orders spans: dep layer ascending, then conflict degree descending, then id
   ascending (deterministic tie-break).
4. Greedily assigns each span to the earliest wave where it fits (no conflict,
   above its dependency floor, below the width cap).

Conflict rule - two spans cannot share a wave if any of the following holds:

- Their `modules` arrays intersect (module overlap).
- Both have `touchesContracts: true` AND their `contractTypes` arrays intersect
  (shared added contract type).
- Either has `touchesSharedKernel: true`.

## Span record schema

One record per workflow sweep (work item). All fields are required except the
optional `status` (omit it, or set it to a non-`"done"` value, for active work).

```json
{
  "id":                 "WF-XX-01",
  "modules":            ["module-a", "module-b"],
  "touchesContracts":   false,
  "contractTypes":      [],
  "touchesSharedKernel": false,
  "dependsOn":          [],
  "backlogScreenDep":   null,
  "confidence":         "mined",
  "status":             "pending"
}
```

| Field | Type | Meaning |
|---|---|---|
| `id` | string | Unique workflow identifier (e.g. `WF-XX-01`). |
| `modules` | string[] | Module codes this sweep traverses. |
| `touchesContracts` | bool | True if the sweep adds or modifies integration contracts. |
| `contractTypes` | string[] | The contract type identifiers added (empty unless `touchesContracts`). |
| `touchesSharedKernel` | bool | True if the sweep modifies SharedKernel (forces solo wave). |
| `dependsOn` | string[] | IDs of spans that must complete before this one starts. |
| `backlogScreenDep` | string|null | Screen that must be reconstructed first (displayed in plan). |
| `confidence` | string | `"mined"` = derived from registry; `"manual"` = hand-set. |
| `status` | string (optional) | `"done"` = integrated; the span is dropped from the schedule and listed under "Completed", and counts as a satisfied dependency for the remaining work. Omit (or any other value) for active work. Lets the plan stay accurate as waves land without deleting their rows. |

## How to mine spans.json

Derive one span record per workflow from your `WORKFLOW_REGISTRY.md`:

1. List the modules each workflow traverses -> `modules`.
2. If it adds or modifies events/DTOs in `Contracts/` -> set `touchesContracts: true`
   and list the contract type names in `contractTypes`.
3. If it touches `SharedKernel/` -> `touchesSharedKernel: true`.
4. Note which workflows must finish first -> `dependsOn`.
5. Note any screen that must be reconstructed before the sweep -> `backlogScreenDep`.

Do NOT ship `spans.json` in the kit - it is project data. The kit ships only
`spans.fixture.json` (the 6-span test fixture) and this how-to.

## Running the scheduler

### Dot-source mode (programmatic)

```powershell
. tools/Compute-WaveSchedule.ps1
$spans = Get-Content spans.json -Raw | ConvertFrom-Json
$waves = Get-WaveSchedule -Spans $spans -WidthCap 5
```

`Get-WaveSchedule` parameters:

| Parameter | Type | Default | Description |
|---|---|---|---|
| `-Spans` | object[] | required | Array of span records (from ConvertFrom-Json). |
| `-WidthCap` | int | 5 | Maximum number of sweeps per wave. |

Returns: an array of string arrays, each inner array being the span IDs in one wave.

### Render mode (write WAVE_PLAN.md)

Place your `spans.json` alongside the script (or adjust `$PSScriptRoot` paths),
then run:

```powershell
powershell -NoProfile -File tools/Compute-WaveSchedule.ps1 -Render
powershell -NoProfile -File tools/Compute-WaveSchedule.ps1 -Render -WidthCap 3
```

Top-level parameters:

| Parameter | Type | Default | Description |
|---|---|---|---|
| `-Render` | switch | off | When present: reads `spans.json` from the script dir, schedules, writes `docs/WAVE_PLAN.md` two levels up from the script dir. |
| `-WidthCap` | int | 5 | Maximum sweeps per wave (passed through to `Get-WaveSchedule`). |

The output path is `<repo-root>/docs/WAVE_PLAN.md` where `<repo-root>` is
resolved as `$PSScriptRoot/../..`. Adjust the `$repoRoot` / `$outPath` lines in
the script if your directory layout differs.

Re-run on drift: edit the changed span row in `spans.json`, re-run with `-Render`,
re-commit `WAVE_PLAN.md` + the registry. The schedule is a living artifact.

## Wave disjointness verifier (the pre-fan-out hard gate)

`Verify-WaveDisjoint.ps1` is the Phase 0 hard gate of the wave runner
(`/build-wave`): before any sweep in a wave fans out, it re-confirms the wave is
still pairwise parallel-safe under the **same** conflict rule the scheduler uses
(it dot-sources `Compute-WaveSchedule.ps1`, so the rule can never drift between
planning and verifying). It also reports any span whose real Enrich **widened**
it beyond the baseline recorded in `spans.json` — the signal to patch
`spans.json` and re-run the scheduler before proceeding.

At dispatch, the wave's `spec-architect` agents emit their true spans; assemble
them into an emitted-spans JSON (same schema as `spans.json` rows) and run:

```powershell
powershell -NoProfile -File tools/Verify-WaveDisjoint.ps1 -Verify -EmittedPath <wave-emitted.json>
```

| Exit | Meaning | Action |
|---|---|---|
| 0 | Wave stays disjoint | Proceed with fan-out. |
| 1 | A pairwise conflict was found | Re-plan: patch `spans.json`, re-run `Compute-WaveSchedule.ps1 -Render`, drop the conflicting sweep to a later wave. **Never fan out on a stale assumption.** |
| 2 | Bad arguments (missing `-EmittedPath`, file not found) | Fix the invocation. |

A non-fatal `WIDENED spans` notice prints before the verdict whenever an emitted
span exceeds its `spans.json` baseline; treat it as a prompt to refresh the plan.

## Running the tests

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tools/Schedule.Tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tools/Verify-WaveDisjoint.Tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tools/RenderFilter.Tests.ps1
```

Expected output (one per file):
- `PASS: all scheduling invariants hold (3 waves)`
- `PASS: all wave-disjointness verifier invariants hold`
- `PASS: render-filter active/done partition holds`

The fixture (`spans.fixture.json`) exercises six invariants with six spans (A-1
through F-1): module overlap conflict, shared contract type conflict, dependency
ordering, co-wave safety, and width cap. `Verify-WaveDisjoint.Tests.ps1` reuses
the same fixture and also asserts the verifier's conflict rule never drifts from
the scheduler's. `RenderFilter.Tests.ps1` checks the `status: "done"`
active/completed partition over synthetic spans.

## Reference

For a worked project example (full workflow list, wave table, throughput model,
and rationale for the width cap choice), see the design rationale recorded in
your project's planning notes (the document that produced this kit is project-specific
and will not be present in a fresh installation).
