fix: reject dry-run dispatch as continuity receipt
This commit is contained in:
@@ -355,12 +355,7 @@ function buildApprovedPlanContinuityInput(wrapperResult: any, autoChainPlanResul
|
|||||||
? wrapperResult.replyClosureState
|
? wrapperResult.replyClosureState
|
||||||
: (wrapperResult?.handoff?.mode === "button_path" ? "waiting_user" : "completed");
|
: (wrapperResult?.handoff?.mode === "button_path" ? "waiting_user" : "completed");
|
||||||
|
|
||||||
const dispatchReceipt = wrapperResult?.dispatchReceipt ?? (autoChainPlanResult?.dispatchMode && autoChainPlanResult.dispatchMode !== "no_dispatch"
|
const dispatchReceipt = wrapperResult?.dispatchReceipt ?? null;
|
||||||
? {
|
|
||||||
dispatchMode: autoChainPlanResult.dispatchMode,
|
|
||||||
derivedAction: autoChainPlanResult.derivedAction,
|
|
||||||
}
|
|
||||||
: null);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
planId: wrapperResult?.planId ?? "hook-preflight-approved-plan",
|
planId: wrapperResult?.planId ?? "hook-preflight-approved-plan",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const handlerPath = path.join(repoRoot, 'hooks', 'force-recall', 'handler.ts');
|
|||||||
const wrapperPath = path.join(repoRoot, 'scripts', 'long_task_governor_wrapper.mjs');
|
const wrapperPath = path.join(repoRoot, 'scripts', 'long_task_governor_wrapper.mjs');
|
||||||
const gateLockPath = path.join(repoRoot, 'scripts', 'long_task_gate_lock.mjs');
|
const gateLockPath = path.join(repoRoot, 'scripts', 'long_task_gate_lock.mjs');
|
||||||
const plannerPath = path.join(repoRoot, 'scripts', 'plan_long_task_auto_chain.mjs');
|
const plannerPath = path.join(repoRoot, 'scripts', 'plan_long_task_auto_chain.mjs');
|
||||||
|
const continuityGatePath = path.join(repoRoot, 'scripts', 'approved_plan_continuity_gate.mjs');
|
||||||
const execFileAsync = promisify(execFileCallback);
|
const execFileAsync = promisify(execFileCallback);
|
||||||
|
|
||||||
async function importTsModule(tsPath) {
|
async function importTsModule(tsPath) {
|
||||||
@@ -54,6 +55,7 @@ async function prepareTempWorkspace() {
|
|||||||
[wrapperPath, path.join(tempWorkspace, 'scripts', 'long_task_governor_wrapper.mjs')],
|
[wrapperPath, path.join(tempWorkspace, 'scripts', 'long_task_governor_wrapper.mjs')],
|
||||||
[gateLockPath, path.join(tempWorkspace, 'scripts', 'long_task_gate_lock.mjs')],
|
[gateLockPath, path.join(tempWorkspace, 'scripts', 'long_task_gate_lock.mjs')],
|
||||||
[plannerPath, path.join(tempWorkspace, 'scripts', 'plan_long_task_auto_chain.mjs')],
|
[plannerPath, path.join(tempWorkspace, 'scripts', 'plan_long_task_auto_chain.mjs')],
|
||||||
|
[continuityGatePath, path.join(tempWorkspace, 'scripts', 'approved_plan_continuity_gate.mjs')],
|
||||||
[handlerPath, path.join(tempWorkspace, 'hooks', 'force-recall', 'handler.ts')],
|
[handlerPath, path.join(tempWorkspace, 'hooks', 'force-recall', 'handler.ts')],
|
||||||
[path.join(repoRoot, 'docs', 'RULEBOOK.md'), path.join(tempWorkspace, 'docs', 'RULEBOOK.md')],
|
[path.join(repoRoot, 'docs', 'RULEBOOK.md'), path.join(tempWorkspace, 'docs', 'RULEBOOK.md')],
|
||||||
[path.join(repoRoot, 'SOUL.md'), path.join(tempWorkspace, 'SOUL.md')],
|
[path.join(repoRoot, 'SOUL.md'), path.join(tempWorkspace, 'SOUL.md')],
|
||||||
@@ -104,7 +106,7 @@ function buildWrapperScript(wrapperResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await Promise.all([fs.access(wrapperPath), fs.access(gateLockPath), fs.access(plannerPath)]);
|
await Promise.all([fs.access(wrapperPath), fs.access(gateLockPath), fs.access(plannerPath), fs.access(continuityGatePath)]);
|
||||||
const { default: forceRecall } = await importTsModule(handlerPath);
|
const { default: forceRecall } = await importTsModule(handlerPath);
|
||||||
assert.equal(typeof forceRecall, 'function', 'force-recall handler should export default function');
|
assert.equal(typeof forceRecall, 'function', 'force-recall handler should export default function');
|
||||||
|
|
||||||
@@ -307,6 +309,12 @@ async function main() {
|
|||||||
assert.match(passInjected, /derivedAction=dispatch_spec_review/, 'hook pass-path should derive dry-run spec review dispatch');
|
assert.match(passInjected, /derivedAction=dispatch_spec_review/, 'hook pass-path should derive dry-run spec review dispatch');
|
||||||
assert.match(passInjected, /dispatchMode=dry_run_dispatch/, 'hook pass-path should stay in dry-run dispatch mode');
|
assert.match(passInjected, /dispatchMode=dry_run_dispatch/, 'hook pass-path should stay in dry-run dispatch mode');
|
||||||
assert.match(passInjected, /autoChainAllowed=true/, 'hook pass-path should allow auto-chain in dry-run planner output');
|
assert.match(passInjected, /autoChainAllowed=true/, 'hook pass-path should allow auto-chain in dry-run planner output');
|
||||||
|
assert.match(passInjected, /\[APPROVED_PLAN_CONTINUITY_GATE\]/, 'hook pass-path should emit approved-plan continuity gate block');
|
||||||
|
assert.match(passInjected, /status=continuity_failure/, 'hook pass-path should fail continuity when planner only returns dry-run dispatch without a real receipt');
|
||||||
|
assert.match(passInjected, /verdict=continuity_failure/, 'hook pass-path should expose continuity failure verdict when no real dispatch receipt exists');
|
||||||
|
assert.match(passInjected, /reason=missing_dispatch_receipt/, 'hook pass-path should require a real dispatch receipt instead of treating dry-run dispatch as one');
|
||||||
|
assert.match(passInjected, /Route back to continuity failure until a real next dispatch receipt exists/, 'hook pass-path should hard-gate normal closeout until a real receipt exists');
|
||||||
|
assert.doesNotMatch(passInjected, /\[APPROVED_PLAN_CONTINUITY_GATE\][\s\S]*status=pass/, 'hook pass-path should not let approved-plan continuity pass on dry-run dispatch alone');
|
||||||
|
|
||||||
const failInjected = await withPatchedWrapper(buildWrapperScript({
|
const failInjected = await withPatchedWrapper(buildWrapperScript({
|
||||||
classification: 'long_task',
|
classification: 'long_task',
|
||||||
|
|||||||
Reference in New Issue
Block a user