Full suite finished green; here is the report.

```
STATE: wf/cpu-orchestration-cleanup | base c751c17 (dedicated branch, parent feat/cpu-orchestration @36241da is an ancestor) | tree dirty: 1 untracked file, .phased/active/cpu-orchestration-cleanup/log/quality-check-agent.txt (this agent's own tee) | phases 5 done / 5, no [!]/[~]/[>]
  One extra commit after phase 5: 9572c63 "wf: settle the review's prose leftovers and realign the phase-1 contract copy" — it settled 8 of review.md's 10 flagged rows.
  Re-run at HEAD: ruff check src/ tests/ → All checks passed. pytest tests/ -q → 1781 passed, 3 warnings, 118s. No git grep for wf:phase- markers matches anywhere.

DONE-CHECK:
  Phase 1 — MET. psutil probe test module 14 passed with the temperature meter; /proc + PROCESS_STAT_ROOT + PROCESS_CLOCK_TICKS absent from worker_handler.py; pyproject.toml:40 "psutil>=6.0" (one line); in-tree phase-1 contract test byte-identical to the plan copy. See finding M6 on HOW that identity was reached.
  Phase 2 — MET, with one benign grep hit. pytest of the three named modules 58 passed. `git grep -c 'cpu_grow_'` prints tests/test_group_policy_admission_names.py:7 — the phase-2 contract test, whose job is to assert the old keys are refused. `git grep -c 'growth threshold' -- src` prints nothing. Contract copy byte-identical.
  Phase 3 — MET, with benign grep hits. pytest of the four named modules 101 passed. The `Done:` grep prints 6 lines: 5 in the phase-3 contract test (assertions of absence) and tests/orchestration/test_orchestration_cpu_growth.py:539, which is a test NAME whose tail is "..._blocked_worker_has_room" — the regex `_has_room` matching prose, not a surviving callable. No deleted method exists. Contract copy byte-identical.
  Phase 4 — MET, same shape. pytest of the four named modules 37 passed. The grep prints 5 lines, all in the phase-4 contract test. Contract copy byte-identical.
  Phase 5 — MET. review.md exists with its three sections; ruff clean; suite green.
  NOT RE-RUNNABLE: the baseline-attribution clause of every phase ("no failure that is not also failing on feat/cpu-orchestration @36241da") — comparing needs a second worktree at that commit, which is a write this agent may not make. It is moot: HEAD has zero failures, so there is nothing to attribute.

FINDINGS:

  MECHANICAL:
  - src/genro_asgi/spa/orchestration/group_handler.py:1013-1018 — the empty-group birth runs OUTSIDE `_placement_lock` and without the `_policy_held(policy, "grow")` checkpoint. The `_grow` it replaced held both ("Under the placement lock, like every other road to a birth"). Failure: a beat finds `living_workers` empty and awaits `start_worker()`; `worker_handler_map[name]` is written before the await, but the newborn is `starting`, and `WorkerHandler.assign_user:357` refuses anything not `running`. A request arriving in that window takes the lock, is refused by the newborn, passes `_may_grow`, and forks a SECOND worker for one need — the exact double-fork the lock existed to prevent. Cross-phase: Phase 3 deleted the road and did not carry its two guards to the new one. Related: tests/orchestration/test_orchestration_policy_delegation.py:46 still lists `check_occupancy` in SNAPSHOT_DECISIONS, but its one surviving effect no longer consults the snapshot.
  - tests/orchestration/test_orchestration_m4_e2e.py:82,87 — `from genro_asgi.spa.orchestration import AssignmentRefused` sits alone above the first-party block and duplicates line 86, `from genro_asgi.spa.orchestration import FreezeHandler, GroupHandler` (same module, two statements); line 87 `...worker_handler` precedes line 88 `...spa_commander`, out of order. Phase 1 added one import and Phase 3/4 the other, neither seeing the other. ruff's select is `E4,E7,E9,F`, so isort never fires.
  - Ten lines added by this workflow exceed pyproject.toml:91 `line-length = 100`, longest group_handler.py:165 (134 chars), :329 (117), :1433 (115), plus group_policy.py:208-209,220 and four in tests/test_group_policy.py. E501 is not in the ruff select list, so Phase 5's `ruff check` gate could not see them. Phase 2's rename lengthened the identifiers; nothing rewrapped.
  - tests/test_group_policy.py:48 — `# wf:contract: (rearm >= grow);` still carries both pre-Phase-2 names. Phase 5 flagged the same word at group_handler.py:1557 and missed this one, though the file is in its own reviewed set.
  - src/genro_asgi/spa/orchestration/worker_handler.py:78-80 — the module docstring reads "the commander reads this process's cumulative kernel clock through the handler, through psutil, and keeps the two-reading anchor here. That". Phase 1 spliced "through psutil" into the sentence: two `through` clauses, a two-word orphan line, and "kernel clock" still naming a reading that no longer comes from the kernel row. Not in review.md.
  - .phased/active/cpu-orchestration-cleanup/tests/phase-1/ — `git diff c751c17 HEAD -- <that dir>` is NOT empty: 9572c63 edited the plan copy of the phase-1 contract test. contracts.md requires any such divergence to be covered by a decision recorded in notes.md under `## Phase 1`; there is none. The edit itself is sound (it is the owner's resolution of verify.md's second item), only the record is missing.

  JUDGMENT:
  - src/genro_asgi/spa/orchestration/group_handler.py:1015 — the empty-group birth ignores `_may_grow`. The plan's third `Must not break:` line says "`_may_grow` stays the memory veto on the birth"; Phase 3's own Decisions and notes.md prescribe the unconditional `await self.start_worker()` ("the group's existence, not speculative capacity"). Under an exhausted quota a group whose last worker died now forks its reception where the old road wrote `saturated` instead. Deliberate and pre-declared, but it is the one place this workflow steps outside its own `Must not break:` — your call whether the next plan inherits it as-is.
  - group_handler.py:93 and :1433 now describe `close_occupancy_max_percent` as sitting "distinctly below the admission close setpoint". The referent is `occupancy_max_percent`, but "admission close" is the plain-English reading of `cpu_admission_close_percent` — Phase 2's new name, and a different currency (CPU, not occupancy). spa_app.py:257 still calls the same relation "the growth setpoint". One fact, three names, two phases pulling opposite ways.
  - group_handler.py:730-741 — the journal order `grow` survives only on the saturation. `order=grow ... outcome=saturated` is now the ONLY `grow` row a sysop ever sees, emitted at the one spot where nothing grows; the successful birth logs `start_worker` plus reason `new_worker_created_for_placement`. The `Must not break:` line permits it (the order name is not a reason code), but the word now labels its opposite.
  - tests/orchestration/test_orchestration_cpu_growth.py:471, `test_the_reactive_growth_and_a_placement_cannot_fork_twice` — the reactive growth is gone, so the `check_occupancy` half of its `asyncio.gather` births nothing (one worker lives, so the empty-group branch never fires) and `len(...) == 2` is now true by construction. The test kept its name and its green while losing its second racer. `test_a_crossing_and_a_placement_race_to_one_spawn:447` is the milder version of the same.
  - review.md → *Flagged for human*: 8 of its 10 rows are already settled on HEAD by 9572c63, so verify.md's two `now` items are stale as written. What actually remains is two lines, both in group_handler.py: :36-37 "the wake rings on the way out, so the group grows before he tries again" (the wake now lifts the saturation; it grows nobody) and :1557 "(churn of 2026-08-28, rearm30)".
  - Not a finding, verified and worth recording: tests/orchestration/test_orchestration_m3_e2e.py needs no `get_process_cpu_reading` patch. Its `group` fixture builds SpaCommander and GroupHandler directly and never starts `cpu_meter_loop`, so `get_cpu_temperature_percent()` stays None. Only m4, which boots the real AsgiServer, was exposed — Phase 1 patched exactly the one that needed it.

VERIFY-NOTES:
  From plan.md, Phase 5, when = now:
    "> Verify: now — read `review.md` → *Flagged for human*: nine docstrings and comments still describe the periodic growth, the reception's reserve, `/proc`/Linux or the `rearm` name. Each rewrite is the owner's wording, and each sits in code the next plan reopens."
  From verify.md, Phase 5, both when = now:
    "- **now** — read `review.md` → *Flagged for human*. Nine docstrings and comments in the files Phases 1..4 touched still describe the periodic growth, the reception's reserve, `/proc`/Linux as the CPU source, or the `rearm` name. Each entry carries the line and a suggested rewrite; the wording is the owner's, and the code around it is what the next plan reopens."
    "- **now** — decide the phase-1 contract test divergence: the in-tree copy of `tests/orchestration/test_orchestration_cpu_meter_psutil.py:68` carries `# the name before Phase 2` on a line the Phase 2 rename already changed. Either drop the comment in both copies, or update the plan copy under `.phased/active/cpu-orchestration-cleanup/tests/phase-1/`."
  Both are stale against HEAD — see the last JUDGMENT entry. The second was resolved by 9572c63 (comment dropped in both copies); the first is down to two lines.
  From plan.md, `> Review:` notes — all four confirmed, none dismissed:
    Phase 1's `cpu_temperature_sample_seconds` gap is real and untouched: it is declared as a `commander` grammar kwarg in spa_app.py but absent from `commander_kwargs` in config/handler.py, so a recipe declaring it is silently ignored. Correctly left out of scope.
    Phase 2's two notes (the unfalsifiable assertion fixed to `"order=grow" not in row`; the phase-1 contract divergence) — both confirmed, the second now settled.
    Phase 3's note on the three adapted out-of-list tests — confirmed against the diff; the m4 growth steps do call `start_worker()` directly and step 8 provokes the saturation with a refused `assign_user`.
    Phase 4's note on the two e2e stories — confirmed: the concessions drop (m3 100→80MB, m4 100→80MB and 85→26MB), `declared_rss_bytes` is lowered to SPARE_RSS_BYTES before each birth, and every assertion of both stories survives with the arithmetic re-derived (35/40 = 87.5%).
  notes.md has no `## Run inspection` section.
```
