feat(gate): verify_required levels — ALIGNED/ALIGNED_OR_PARTIAL/ANY (#16)

Sluit story 'Verify-gate uitbreiden' in PBI 'Agent verify-flow hardening' af.

The previous gate weighed only EMPTY: any PARTIAL or DIVERGENT verify
slipped through. The Insights batch (2 May 2026) showed why that's
weak — agent-jobs claiming DONE while only delivering helpers, not
the requested UI components, with verify=DIVERGENT/PARTIAL accepted.

New decision matrix:

  null                       → reject (run verify_task_against_plan)
  EMPTY  + !verify_only      → reject
  EMPTY  + verify_only       → allowed
  ALIGNED                    → always allowed
  PARTIAL/DIVERGENT
    required=ALIGNED         → reject (strict task)
    required=ALIGNED_OR_PARTIAL (default) → allowed only if summary
                                            ≥20 chars (acknowledge drift)
    required=ANY             → allowed (refactor escape hatch)

`update_job_status('done')` now reads `task.verify_required` from the DB
(field added in Scrum4Me PR #53) and passes it + `summary` to the gate.
Tool description updated with the new rules.

Vendor submodule synced to pick up the schema enum.

Tests: 129/129 (was 120 + 9 new combinatorial gate tests).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-02 17:55:06 +02:00 committed by GitHub
parent 0bcca15235
commit 1fe6ccf609
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 131 additions and 33 deletions

View file

@ -34,6 +34,19 @@ enum ClaudeJobStatus {
CANCELLED
}
enum VerifyResult {
ALIGNED
PARTIAL
EMPTY
DIVERGENT
}
enum VerifyRequired {
ALIGNED
ALIGNED_OR_PARTIAL
ANY
}
enum TaskStatus {
TO_DO
IN_PROGRESS
@ -57,13 +70,6 @@ enum SprintStatus {
COMPLETED
}
enum VerifyResult {
ALIGNED
PARTIAL
EMPTY
DIVERGENT
}
model User {
id String @id @default(cuid())
username String @unique
@ -219,6 +225,8 @@ model Sprint {
product_id String
sprint_goal String
status SprintStatus @default(ACTIVE)
start_date DateTime? @db.Date
end_date DateTime? @db.Date
created_at DateTime @default(now())
completed_at DateTime?
stories Story[]
@ -240,8 +248,9 @@ model Task {
priority Int
sort_order Float
status TaskStatus @default(TO_DO)
verify_only Boolean @default(false)
created_at DateTime @default(now())
verify_only Boolean @default(false)
verify_required VerifyRequired @default(ALIGNED_OR_PARTIAL)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
claude_questions ClaudeQuestion[]
claude_jobs ClaudeJob[]
@ -266,12 +275,12 @@ model ClaudeJob {
started_at DateTime?
finished_at DateTime?
pushed_at DateTime?
verify_result VerifyResult?
plan_snapshot String?
branch String?
pr_url String?
summary String?
error String?
verify_result VerifyResult?
retry_count Int @default(0)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@ -279,6 +288,7 @@ model ClaudeJob {
@@index([user_id, status])
@@index([task_id, status])
@@index([status, claimed_at])
@@index([status, finished_at])
@@map("claude_jobs")
}