feat(voyage): validate iterations_remaining in progress-validator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d8a5e8c5e
commit
e986b10431
2 changed files with 46 additions and 0 deletions
|
|
@ -1,5 +1,11 @@
|
|||
// lib/validators/progress-validator.mjs
|
||||
// Validate progress.json shape + resume-readiness.
|
||||
// Forward-compat: unknown keys are tolerated silently, so additive-optional fields land
|
||||
// WITHOUT a schema_version bump. `iterations_remaining` (trekexecute's recovery/retry budget
|
||||
// signal) is shape-checked here only when present (non-negative integer); it is NOT in
|
||||
// PROGRESS_REQUIRED_TOP, so legacy progress.json without it still validates. The field's
|
||||
// liveness (never-decremented) is enforced deterministically by the Phase 7.5 completion-gate
|
||||
// cross-check in trekexecute, not by this per-validation shape check.
|
||||
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { issue, fail } from '../util/result.mjs';
|
||||
|
|
@ -45,6 +51,16 @@ export function validateProgressObject(parsed, opts = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Additive-optional: iterations_remaining is trekexecute's recovery/retry budget signal.
|
||||
// When present it must be a non-negative integer; absence is valid (backward-compat).
|
||||
if (parsed.iterations_remaining !== undefined &&
|
||||
!(Number.isInteger(parsed.iterations_remaining) && parsed.iterations_remaining >= 0)) {
|
||||
errors.push(issue(
|
||||
'PROGRESS_ITERATIONS_REMAINING_INVALID',
|
||||
`iterations_remaining=${parsed.iterations_remaining} must be a non-negative integer`,
|
||||
));
|
||||
}
|
||||
|
||||
if (parsed.steps && typeof parsed.steps === 'object') {
|
||||
const stepKeys = Object.keys(parsed.steps);
|
||||
if (typeof parsed.total_steps === 'number' && stepKeys.length !== parsed.total_steps) {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,36 @@ test('validateProgress — step count mismatch is warning', () => {
|
|||
assert.ok(r.warnings.find(w => w.code === 'PROGRESS_STEP_COUNT_MISMATCH'));
|
||||
});
|
||||
|
||||
test('validateProgress — iterations_remaining non-negative integer is valid', () => {
|
||||
const p = goodProgress();
|
||||
p.iterations_remaining = 7;
|
||||
const r = validateProgressObject(p);
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('validateProgress — iterations_remaining absent is valid (backward-compat)', () => {
|
||||
const p = goodProgress();
|
||||
delete p.iterations_remaining;
|
||||
const r = validateProgressObject(p);
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('validateProgress — iterations_remaining negative is invalid', () => {
|
||||
const p = goodProgress();
|
||||
p.iterations_remaining = -1;
|
||||
const r = validateProgressObject(p);
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(r.errors.find(e => e.code === 'PROGRESS_ITERATIONS_REMAINING_INVALID'));
|
||||
});
|
||||
|
||||
test('validateProgress — iterations_remaining non-integer is invalid', () => {
|
||||
const p = goodProgress();
|
||||
p.iterations_remaining = 3.5;
|
||||
const r = validateProgressObject(p);
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(r.errors.find(e => e.code === 'PROGRESS_ITERATIONS_REMAINING_INVALID'));
|
||||
});
|
||||
|
||||
test('checkResumeReadiness — completed run cannot resume', () => {
|
||||
const p = goodProgress();
|
||||
p.status = 'completed';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue