Merge pull request #6 from madhura68/fix/done-gate-reject-empty-verify
fix: DONE-gate in update_job_status (verify_result null/EMPTY rejection)
This commit is contained in:
commit
c990d8547e
2 changed files with 73 additions and 0 deletions
39
__tests__/update-job-status-gate.test.ts
Normal file
39
__tests__/update-job-status-gate.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { checkVerifyGate } from '../src/tools/update-job-status.js'
|
||||
|
||||
describe('checkVerifyGate', () => {
|
||||
it('rejects when verify_result is null — agent must verify first', () => {
|
||||
const r = checkVerifyGate(null, false)
|
||||
expect(r.allowed).toBe(false)
|
||||
if (!r.allowed) expect(r.error).toMatch(/verify_task_against_plan/i)
|
||||
})
|
||||
|
||||
it('rejects when verify_result is EMPTY and task is not verify_only', () => {
|
||||
const r = checkVerifyGate('EMPTY', false)
|
||||
expect(r.allowed).toBe(false)
|
||||
if (!r.allowed) {
|
||||
expect(r.error).toMatch(/EMPTY/i)
|
||||
expect(r.error).toMatch(/verify_only/i)
|
||||
}
|
||||
})
|
||||
|
||||
it('allows when verify_result is EMPTY and task IS verify_only', () => {
|
||||
const r = checkVerifyGate('EMPTY', true)
|
||||
expect(r.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('allows when verify_result is ALIGNED', () => {
|
||||
const r = checkVerifyGate('ALIGNED', false)
|
||||
expect(r.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('allows when verify_result is PARTIAL', () => {
|
||||
const r = checkVerifyGate('PARTIAL', false)
|
||||
expect(r.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('allows when verify_result is DIVERGENT', () => {
|
||||
const r = checkVerifyGate('DIVERGENT', false)
|
||||
expect(r.allowed).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -91,6 +91,27 @@ export async function prepareDoneUpdate(
|
|||
}
|
||||
}
|
||||
|
||||
export function checkVerifyGate(
|
||||
verifyResult: string | null,
|
||||
verifyOnly: boolean,
|
||||
): { allowed: true } | { allowed: false; error: string } {
|
||||
if (verifyResult === null) {
|
||||
return {
|
||||
allowed: false,
|
||||
error: 'Roep eerst verify_task_against_plan aan voordat je DONE markeert.',
|
||||
}
|
||||
}
|
||||
if (verifyResult === 'EMPTY' && !verifyOnly) {
|
||||
return {
|
||||
allowed: false,
|
||||
error:
|
||||
'Plan-vs-implementatie verify gaf EMPTY. Geen wijzigingen gedetecteerd. ' +
|
||||
'Markeer de task als verify_only of pas de implementatie aan.',
|
||||
}
|
||||
}
|
||||
return { allowed: true }
|
||||
}
|
||||
|
||||
const DB_STATUS_MAP = {
|
||||
running: 'RUNNING',
|
||||
done: 'DONE',
|
||||
|
|
@ -140,6 +161,8 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
'Report progress on a claimed ClaudeJob. Allowed transitions from CLAIMED/RUNNING: ' +
|
||||
'running (start), done (finished), failed (error). ' +
|
||||
'The Bearer token must match the token that claimed the job. ' +
|
||||
'Before marking done: call verify_task_against_plan first — done is rejected when ' +
|
||||
'verify_result is null or EMPTY (unless task.verify_only is true). ' +
|
||||
'Automatically emits an SSE event so the Scrum4Me UI updates in real time.',
|
||||
inputSchema,
|
||||
},
|
||||
|
|
@ -157,6 +180,8 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
user_id: true,
|
||||
product_id: true,
|
||||
task_id: true,
|
||||
verify_result: true,
|
||||
task: { select: { verify_only: true } },
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -176,6 +201,12 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
let skipWorktreeCleanup = false
|
||||
|
||||
if (status === 'done') {
|
||||
const gate = checkVerifyGate(
|
||||
job.verify_result ?? null,
|
||||
job.task?.verify_only ?? false,
|
||||
)
|
||||
if (!gate.allowed) return toolError(gate.error)
|
||||
|
||||
const plan = await prepareDoneUpdate(job_id, branch)
|
||||
actualStatus = plan.dbStatus === 'DONE' ? 'done' : 'failed'
|
||||
pushedAt = plan.pushedAt
|
||||
|
|
@ -223,6 +254,7 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
branch: true,
|
||||
pushed_at: true,
|
||||
pr_url: true,
|
||||
verify_result: true,
|
||||
summary: true,
|
||||
error: true,
|
||||
started_at: true,
|
||||
|
|
@ -247,6 +279,7 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
branch: updated.branch ?? undefined,
|
||||
pushed_at: updated.pushed_at?.toISOString() ?? undefined,
|
||||
pr_url: updated.pr_url ?? undefined,
|
||||
verify_result: updated.verify_result?.toLowerCase() ?? undefined,
|
||||
summary: updated.summary ?? undefined,
|
||||
error: updated.error ?? undefined,
|
||||
}),
|
||||
|
|
@ -268,6 +301,7 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
branch: updated.branch,
|
||||
pushed_at: updated.pushed_at?.toISOString() ?? null,
|
||||
pr_url: updated.pr_url ?? null,
|
||||
verify_result: updated.verify_result?.toLowerCase() ?? null,
|
||||
summary: updated.summary,
|
||||
error: updated.error,
|
||||
started_at: updated.started_at?.toISOString() ?? null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue