mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
Add scheduler flow parity validation script
This commit is contained in:
1 parent
f195406b47
commit
e4f700bdab
2 files changed
+196
-1
No files matched your search
+1
-1
@@ -36,7 +36,7 @@
|
||||
"build": "node build.mjs",
|
||||
"lint": "eslint .",
|
||||
"lock:complete": "node bin/torch-lock.mjs complete",
|
||||
"validate:scheduler": "node scripts/validate-scheduler-roster.mjs && node scripts/validate-prompt-contract.mjs",
|
||||
"validate:scheduler": "node scripts/validate-scheduler-roster.mjs && node scripts/validate-prompt-contract.mjs && node scripts/validate-scheduler-flow-parity.mjs",
|
||||
"scheduler:daily": "node scripts/agent/run-scheduler-cycle.mjs daily",
|
||||
"scheduler:weekly": "node scripts/agent/run-scheduler-cycle.mjs weekly"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
const FILES = {
|
||||
meta: 'src/prompts/META_PROMPTS.md',
|
||||
flow: 'src/prompts/scheduler-flow.md',
|
||||
scheduler: 'scripts/agent/run-scheduler-cycle.mjs',
|
||||
};
|
||||
|
||||
function collectMissing(text, requiredSnippets) {
|
||||
return requiredSnippets.filter((snippet) => !text.includes(snippet));
|
||||
}
|
||||
|
||||
function ensureOrdered(text, snippets) {
|
||||
const positions = [];
|
||||
for (const snippet of snippets) {
|
||||
const idx = text.indexOf(snippet);
|
||||
if (idx === -1) {
|
||||
return { ok: false, missing: snippet };
|
||||
}
|
||||
positions.push(idx);
|
||||
}
|
||||
|
||||
for (let i = 1; i < positions.length; i += 1) {
|
||||
if (positions[i] < positions[i - 1]) {
|
||||
return { ok: false, outOfOrder: [snippets[i - 1], snippets[i]] };
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const invariants = [
|
||||
{
|
||||
name: 'lock check + exclusion fallback',
|
||||
promptSnippets: {
|
||||
meta: [
|
||||
'Run preflight to get the exclusion set:',
|
||||
'Use `excluded` from the JSON output as the canonical exclusion set.',
|
||||
'fallback to the union of `locked`, `paused`, and `completed`',
|
||||
],
|
||||
flow: [
|
||||
'canonical exclusion rule',
|
||||
],
|
||||
},
|
||||
schedulerSnippets: [
|
||||
"runCommand('npm', ['run', `lock:check:${cadence}`])",
|
||||
'checkPayload.excluded',
|
||||
'checkPayload.locked',
|
||||
'checkPayload.paused',
|
||||
'checkPayload.completed',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'roster selection source + all-excluded stop',
|
||||
promptSnippets: {
|
||||
meta: [
|
||||
'Read roster from src/prompts/roster.json',
|
||||
'scheduler.firstPromptByCadence.daily',
|
||||
'scheduler.firstPromptByCadence.weekly',
|
||||
'All roster tasks currently claimed by other agents',
|
||||
],
|
||||
flow: [
|
||||
'Roster source: `src/prompts/roster.json`',
|
||||
'Read `scheduler.firstPromptByCadence.<cadence>` from `torch-config.json` if present.',
|
||||
'All roster tasks currently claimed by other agents',
|
||||
],
|
||||
},
|
||||
schedulerSnippets: [
|
||||
"readJson(path.resolve(process.cwd(), 'src/prompts/roster.json')",
|
||||
'parseFrontmatterAgent(content) || parseAgentFromFilename(latestFile)',
|
||||
'scheduler.firstPromptByCadence?.[cadence]',
|
||||
'ALL_EXCLUDED_REASON',
|
||||
"reason: ALL_EXCLUDED_REASON",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'memory retrieve/store execution + required evidence enforcement',
|
||||
promptSnippets: {
|
||||
meta: [
|
||||
'Run required memory workflow for this cadence:',
|
||||
'Validate memory evidence:',
|
||||
'fail the run if either check is missing',
|
||||
],
|
||||
flow: [
|
||||
'Confirm memory contract completion:',
|
||||
'Memory retrieval evidence must exist for this run',
|
||||
'Memory storage evidence must exist for this run',
|
||||
'mode = required`, missing evidence is a hard failure',
|
||||
],
|
||||
},
|
||||
schedulerSnippets: [
|
||||
'schedulerConfig.memoryPolicy.retrieveCommand',
|
||||
'schedulerConfig.memoryPolicy.storeCommand',
|
||||
"name: 'retrieve'",
|
||||
"name: 'store'",
|
||||
"schedulerConfig.memoryPolicy.mode === 'required'",
|
||||
"reason: 'Required memory steps not verified'",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'artifact verification checkpoint',
|
||||
promptSnippets: {
|
||||
flow: [
|
||||
'Verify required run artifacts for the current run window.',
|
||||
'verify-run-artifacts.mjs --since <run-start-iso> --check-failure-notes',
|
||||
'If artifact verification exits non-zero: write `_failed.md` and stop.',
|
||||
],
|
||||
},
|
||||
schedulerSnippets: [
|
||||
"runCommand('node', [",
|
||||
"'scripts/agent/verify-run-artifacts.mjs'",
|
||||
"'--check-failure-notes'",
|
||||
"reason: 'Missing required run artifacts'",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'failure stop conditions + completion ordering',
|
||||
promptSnippets: {
|
||||
meta: [
|
||||
'If any validation command exits non-zero, do not call `lock:complete`',
|
||||
'Publish completion before writing `_completed.md`',
|
||||
'Only after step 10 succeeds, write final task log (`_completed.md`',
|
||||
],
|
||||
flow: [
|
||||
'step 12 MUST NOT be executed (`lock:complete` is forbidden until validation passes)',
|
||||
'Publish completion before writing final success log:',
|
||||
'`_completed.md` MUST be created only after completion publish succeeds.',
|
||||
],
|
||||
},
|
||||
schedulerSnippets: [
|
||||
"reason: 'Validation failed'",
|
||||
'const completeResult = await runCommand(',
|
||||
"['run', 'lock:complete'",
|
||||
"status: 'completed'",
|
||||
'Completion publish failed. Retry npm run lock:complete',
|
||||
"reason: 'Lock backend error'",
|
||||
],
|
||||
orderedSchedulerSnippets: [
|
||||
"reason: 'Validation failed'",
|
||||
'const completeResult = await runCommand(',
|
||||
"status: 'completed'",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const [meta, flow, scheduler] = await Promise.all([
|
||||
readFile(FILES.meta, 'utf8'),
|
||||
readFile(FILES.flow, 'utf8'),
|
||||
readFile(FILES.scheduler, 'utf8'),
|
||||
]);
|
||||
|
||||
const errors = [];
|
||||
|
||||
for (const invariant of invariants) {
|
||||
if (invariant.promptSnippets?.meta) {
|
||||
const missingMeta = collectMissing(meta, invariant.promptSnippets.meta);
|
||||
for (const snippet of missingMeta) {
|
||||
errors.push(`[${invariant.name}] ${FILES.meta} missing snippet: ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (invariant.promptSnippets?.flow) {
|
||||
const missingFlow = collectMissing(flow, invariant.promptSnippets.flow);
|
||||
for (const snippet of missingFlow) {
|
||||
errors.push(`[${invariant.name}] ${FILES.flow} missing snippet: ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
const missingScheduler = collectMissing(scheduler, invariant.schedulerSnippets);
|
||||
for (const snippet of missingScheduler) {
|
||||
errors.push(`[${invariant.name}] ${FILES.scheduler} missing checkpoint snippet: ${snippet}`);
|
||||
}
|
||||
|
||||
if (invariant.orderedSchedulerSnippets) {
|
||||
const order = ensureOrdered(scheduler, invariant.orderedSchedulerSnippets);
|
||||
if (!order.ok && order.missing) {
|
||||
errors.push(`[${invariant.name}] ${FILES.scheduler} missing ordered snippet: ${order.missing}`);
|
||||
} else if (!order.ok && order.outOfOrder) {
|
||||
errors.push(
|
||||
`[${invariant.name}] ${FILES.scheduler} ordering drift: "${order.outOfOrder[0]}" must appear before "${order.outOfOrder[1]}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('Scheduler flow parity drift detected:');
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Scheduler flow parity validated for prompts and scheduler implementation.');
|
||||
Reference in new issue
Block a user