infra: idempotent review controller, verdict reuse, single mutant build, current docs

Review pipeline (process.yml):
- concurrency moves from the workflow to the guard/review jobs and the guard
  runs only for S4-spec-review / S7-code-review. Any other label used to enter
  the issue's concurrency group and evict the pending review run (sample of
  150 runs since 2026-09-01: 92 empty guard-only runs, 30 cancelled).
- the guard reads the issue's current labels instead of the event snapshot; a
  label removed before the run starts is a withdrawn request, no comment.
- a green verdict is re-applied without calling the model when the latest
  review document carries the pipeline-recorded verdict `green`/High 0 and the
  tree differs from its anchor in nothing outside docs/reviews/** (#437 r4
  re-reviewed an unchanged tree for 7 minutes). The verdict from
  structured_output is now written into the anchor block for that purpose.
- the reviewer is pinned to the captured material SHA in the prompt; the
  broken escaping in the "merge cancelled" comment (empty SHAs) is fixed.

Mutation gate: nine browser guards started with `npm run bundle:sync` although
the runner already builds the mutant bundle — a second rollup plus a
`tsc --noEmit` that fails on a non-strict mutant before the smoke even runs.
Prefix removed; `--check` refuses guards that build the bundle themselves.

Docs: SCOPE (Project v2 dropped, three editors), STATUS (#437 merged, HACS zip
automated), USER-GUIDE ru/en (static card shows live states; kiosk double tap
on free background fits all), #34 → #425 references, #367 named as closed in
bundle-budget messages, PROCESS §10.4 and AGENTS.md describe the controller.

Issue: #499
User-Visible: no
This commit is contained in:
Claude
2026-09-09 00:06:27 +03:00
parent ea6061e9fa
commit 24c1b723f9
14 changed files with 344 additions and 48 deletions
+19 -9
View File
@@ -1359,7 +1359,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'vacuum-overlay-back-to-the-dock-space-filter',
guard: 'npm run bundle:sync && node demo/smoke_vacuum_multifloor.mjs',
guard: 'node demo/smoke_vacuum_multifloor.mjs',
because: 'the overlay layer must see every robot of the plan, not only those whose DOCK '
+ 'is in the space on screen: the old filter is exactly why a multi-floor robot could '
+ 'never appear on its second floor (#162, AC2)',
@@ -1383,7 +1383,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'vacuum-manual-fit-after-proposal-uses-the-dock',
guard: 'npm run bundle:sync && node demo/smoke_vacuum_multifloor.mjs',
guard: 'node demo/smoke_vacuum_multifloor.mjs',
because: 'the high-residual proposal is refined against the geometry the matrix was solved '
+ 'against — opening it on the dock floor fits the robot to the wrong plan (#162, AC8, '
+ 'the stateful half the pure calibrationTarget mutant cannot reach)',
@@ -4297,7 +4297,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'near-axis-authoring-snap-bypassed',
guard: 'npm run bundle:sync && node demo/smoke_plan_drawing_repairs.mjs',
guard: 'node demo/smoke_plan_drawing_repairs.mjs',
because: 'the production Walls hover and click must persist the exact same straight endpoint '
+ 'instead of merely repairing old data through Optimize (#290)',
patches: [{
@@ -4331,7 +4331,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'near-axis-optimize-confirmation-bypassed',
guard: 'npm run bundle:sync && node demo/smoke_near_axis_optimize.mjs',
guard: 'node demo/smoke_near_axis_optimize.mjs',
because: 'opening the Optimize preview or cancelling it must never persist a lossy repair (#290)',
patches: [{
file: 'src/houseplan-editor-runtime.ts',
@@ -6242,7 +6242,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'current-rejected-physical-write-keeps-optimistic-wall',
guard: 'npm run bundle:sync && node demo/smoke_v8_draft_write.mjs',
guard: 'node demo/smoke_v8_draft_write.mjs',
because: 'a rejected config/set must synchronously discard its whole pending physical batch '
+ 'before an optimistic active-chain partition survives as a ghost wall (#314/#478)',
patches: [{
@@ -6253,7 +6253,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'area-relocation-loses-position-on-refusal',
guard: 'npm run bundle:sync && node demo/smoke_area_relocation_safety.mjs',
guard: 'node demo/smoke_area_relocation_safety.mjs',
because: 'a rejected provenance write must restore every manual layout point deleted '
+ 'before config/set, or leave explicit attention when restoration also fails (#403)',
patches: [{
@@ -6264,7 +6264,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'area-relocation-clears-whole-history',
guard: 'npm run bundle:sync && node demo/smoke_area_relocation_safety.mjs',
guard: 'node demo/smoke_area_relocation_safety.mjs',
because: 'moving one marker to a new HA Area must invalidate only that marker\'s commands '
+ 'instead of silently erasing Undo and Redo for every other marker (#403)',
patches: [{
@@ -7305,7 +7305,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'furniture-art-editor-adopt-skipped',
guard: 'npm run bundle:sync && node demo/smoke_furniture.mjs',
guard: 'node demo/smoke_furniture.mjs',
because: 'the editor imports the artwork statically and must hand it over synchronously; without '
+ 'adopt the palette previews and the placement ghost render empty on a plan without furniture (#474 r1)',
patches: [{
@@ -7567,7 +7567,7 @@ const MUTANT_DEFINITIONS = [
},
{
id: 'writer-history-skips-finished-chain-normalization',
guard: 'npm run bundle:sync && node demo/smoke_writer_fixed_point.mjs',
guard: 'node demo/smoke_writer_fixed_point.mjs',
because: 'Undo/Redo after a chain has finished must restore canonical snapshots rather than '
+ 'bringing the hidden collinear seams back into durable config (#477)',
patches: [{
@@ -7737,6 +7737,9 @@ export function guardNeedsTestBuild(guard) {
* выполняется только там, где её результат кто-то откроет.
*/
export function guardNeedsBundle(guard) {
// `bundle:sync` в гварде реестр больше не допускает (--check, #499), но
// распознавание остаётся: чужой или старый гвард со сборкой всё равно
// браузерный, и бандл ему нужен.
return guard.includes('demo/') || guard.includes('bundle:sync');
}
@@ -8015,6 +8018,13 @@ function main(argv) {
let stale = 0;
for (const m of selected) {
try {
// #499: бандл мутанта собирает раннер (`buildBundle`, только rollup +
// sync). Гвард с собственным `npm run bundle:sync` собирал бы его второй
// раз — плюс `tsc --noEmit`, который на нестрогом мутанте падает сам и
// красит гвард ещё до теста: «мутант пойман» без единого запуска смока.
if (/bundle:sync|bundle-sync\.mjs|rollup -c/.test(m.guard)) {
throw new Error('гвард сам собирает бандл — сборку делает раннер (#499)');
}
for (const patch of m.patches) {
const source = readFileSync(join(repoRoot, patch.file), 'utf8');
const hits = source.split(patch.find).length - 1;