fix(validators): the progress validator knows stopped and blocked

/trekexecute writes run status stopped (dirty-tree stop, entry-condition stop, escalation) and step status blocked (Step 0 sandbox sentinel); the validator rejected the first and warned on the second. Both are real states lib/util/session-orders.mjs already reads (ENDED_BAD), so the validator learns them. The step-status list is exported as PROGRESS_STEP_STATUSES (was an inline literal) so tests check prose against it instead of keeping a copy. docs/HANDOVER-CONTRACTS.md Handover 5 rows updated.

Red: 3 of 4 new tests fail on the pre-fix code (stopped run status, PROGRESS_STEP_STATUSES export, blocked step status). Regression guard (green on arrival): every run status but completed stays resumable.
progress-validator 16/16. Suite 1368 (1366/0/2).

Voyage order 20261005T104513Z-1250514346, plan step 1/5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-05 17:32:54 +02:00
commit 8a56187341
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
3 changed files with 58 additions and 5 deletions

View file

@ -11,7 +11,11 @@ import { readFileSync, existsSync } from 'node:fs';
import { issue, fail } from '../util/result.mjs';
export const PROGRESS_REQUIRED_TOP = ['schema_version', 'plan', 'plan_version', 'mode', 'status', 'total_steps', 'current_step', 'steps'];
export const PROGRESS_VALID_STATUSES = ['pending', 'in_progress', 'completed', 'failed', 'partial'];
export const PROGRESS_VALID_STATUSES = ['pending', 'in_progress', 'completed', 'failed', 'partial', 'stopped'];
// Step statuses /trekexecute stores. `blocked` is the Step 0 sandbox sentinel; `stopped` above
// is the escalation / dirty-tree / entry-condition stop. Both are states other readers already
// know (lib/util/session-orders.mjs ENDED_BAD).
export const PROGRESS_STEP_STATUSES = ['completed', 'in_progress', 'failed', 'pending', 'deferred', 'skipped', 'blocked'];
export function validateProgressContent(jsonText, opts = {}) {
let parsed;
@ -75,7 +79,7 @@ export function validateProgressObject(parsed, opts = {}) {
errors.push(issue('PROGRESS_STEP_SHAPE', `steps["${k}"] is not an object`));
continue;
}
if (s.status !== undefined && !['completed', 'in_progress', 'failed', 'pending', 'deferred', 'skipped'].includes(s.status)) {
if (s.status !== undefined && !PROGRESS_STEP_STATUSES.includes(s.status)) {
warnings.push(issue('PROGRESS_STEP_BAD_STATUS', `steps["${k}"].status "${s.status}" unrecognized`));
}
}