test(ship-docsync): seed fault cases at their gate instead of replaying attempt 1

The post-dispatch fault cases (missing-marker, launch-failure, timeout-unsettled,
late-result, stale-before, stale-after, recovery) now start from a fixture-owned
attempt 1: the real actor prepares and dispatches it, its verbatim output is saved
once, and the invocation journal carries its pre-dispatch entry with the child
asset hashes. The model resumes at Parent processing with a trimmed read list,
inspect named as the authoritative repository observation, and recovery's
intermediate checkpoint folded into the next attempt's pre-dispatch entry.
Assertions count only parent-issued transport events and require a read of the
saved attempt-1 output; missing-asset and the legacy failure case keep the full
model-driven first attempt, and their prompts are byte-identical.
This commit is contained in:
garrytan committed 2026-09-30 16:30:43 +00:00
1 parent dd5708e6e8
commit fb52689822
3 files changed
+112 -20

No files matched your search

+32
View File
@@ -254,6 +254,38 @@ ${DOCS_CHECKPOINT_MARKER}
return file;
}
export const DOCS_SEEDED_AUDIT_ID = 'ship-docs-a1';
const DOCS_CHILD_ASSETS = ['document-release/SKILL.md', 'document-release/sections/audit-scope.md', 'document-release/sections/release-body.md'];
export function docsActorSeeded(scenario: DocsFault): boolean {
return scenario !== 'missing-asset' && scenario !== 'legacy-completion';
}
export function seedDocsFirstAttempt(fixture: ReturnType<typeof fixtureDocs>, file: string) {
const assets = DOCS_CHILD_ASSETS.map(relative => {
const asset = path.join(fixture.skills, relative);
if (!fs.existsSync(asset)) throw Error(`seeded attempt requires installed ${relative}`);
return { asset, sha256: createHash('sha256').update(fs.readFileSync(asset)).digest('hex') };
});
const prepared = docsActorCommand(file, 'prepare', { audit_id: DOCS_SEEDED_AUDIT_ID });
if (prepared.exit) throw Error(`seeded prepare failed: ${prepared.text}`);
const { candidate, prompt } = JSON.parse(prepared.text) as { candidate: string; prompt: string };
const dispatched = docsActorCommand(file, 'dispatch', { audit_id: DOCS_SEEDED_AUDIT_ID, candidate, prompt, run_in_background: 'false' });
if (dispatched.exit === 24) throw Error(`seeded dispatch rejected: ${dispatched.text}`);
const completion = path.join(fixture.home, `completion-${DOCS_SEEDED_AUDIT_ID}.md`);
fs.writeFileSync(completion, dispatched.text, { flag: 'wx', mode: 0o600 });
let handle = '';
try { handle = JSON.parse(dispatched.text).task_id ?? ''; } catch {}
const record = fs.readFileSync(fixture.invocation, 'utf8');
if (record.split(DOCS_CHECKPOINT_MARKER).length !== 2) throw Error('invocation journal marker must occur exactly once');
fs.writeFileSync(fixture.invocation, record.replace(DOCS_CHECKPOINT_MARKER, () => `### Checkpoint 1 — attempt 1 (fixture-owned prior state)
Attempts used: 1. Prepare step 1: installed child assets present with sha256 ${assets.map(a => `${a.asset} ${a.sha256}`).join('; ')}. Prepare steps 2–3: audit ${DOCS_SEEDED_AUDIT_ID}; candidate ${candidate}; prompt ${prompt}. Launched with dispatch run_in_background=false; dispatch exit code ${dispatched.exit}; its verbatim output is saved once at ${completion}.${handle ? ` Returned child handle: ${handle}.` : ''}
Next: Parent processing for attempt 1, starting at Collect. A repeated Prepare may confirm step 1 with one literal sha256sum of these three asset paths instead of re-reading them; a missing asset or changed hash blocks before launch.
${DOCS_CHECKPOINT_MARKER}`));
return { auditId: DOCS_SEEDED_AUDIT_ID, candidate, prompt, completion, exit: dispatched.exit, text: dispatched.text,
events: load(file).events.length };
}
if (import.meta.main) {
const [action, file, ...rest] = process.argv.slice(2);
if (action === 'hook') docsActorHook(file, fs.readFileSync(0, 'utf8'));