Feature: Validation-gate hook blocks done column without validation proof #210

Closed
opened 2026-03-28 12:22:17 +00:00 by forgejo_admin · 3 comments
Contributor

Type

Feature

Lineage

Standalone -- discovered while documenting the validation column flow in sop-board-workflow and template-validation. The right-side gate (validation -> done) mirrors the left-side gate (todo -> next_up via /review-ticket), but has no hook enforcement yet. The left gate has check-board-item.sh; the right gate has nothing.

Repo

forgejo_admin/claude-custom

User Story

As Betty Sue (PM agent)
I want the done column to reject board items that lack a validation note with PASS verdict
So that no ticket can be marked done without production verification, enforcing the DORA Lead Time definition (clock stops at validation PASS, not at merge).

Context

The kanban board has a validation column between needs_approval and done (per sop-board-workflow). The template-validation note defines the validation note format: slug validation-{issue-number}-{YYYY-MM-DD}, tags validation,pass, and a PASS/PARTIAL/FAIL verdict. Today, nothing prevents an agent from calling update_board_item(column="done") directly, bypassing validation. The board-item-on-merge.sh PostToolUse hook already auto-moves items to done after merge -- this needs to either route through the gate or be updated to move to validation instead.

Enforcement signals available (researched):

  1. pal-e-docs API -- GET /notes?tags=validation,pass filtered by issue number in slug. This is the primary signal. Naming convention: validation-{issue-num}-*.
  2. Forgejo issue labels -- status:approved label exists (set by label-on-verdict.sh). However, this means QA-approved, not production-validated. Not sufficient alone.
  3. Woodpecker pipeline status -- Could check last pipeline succeeded, but this proves CI passed, not production validation. Not the right signal.
  4. Board item's own forgejo_issue_url -- Needed to extract the issue number, which becomes the validation note lookup key.

Decision: Use signal #1 (pal-e-docs validation note existence with pass tag). This is the only signal that proves someone actually ran the validation checks and recorded evidence.

File Targets

Files the agent should modify or create:

  • hooks/gate-validation-done.sh (NEW) -- PreToolUse hook that intercepts update_board_item calls where column=done, extracts the issue number from the board item's forgejo_issue_url, queries pal-e-docs for a validation note with tag pass, and denies if none exists. Follow the pattern in hooks/check-board-item.sh (jq parsing, fail-open, deny with reason).
  • settings.json -- Add a new PreToolUse entry with matcher mcp__pal-e-docs__update_board_item pointing to gate-validation-done.sh.
  • hooks/board-item-on-merge.sh -- Change target column from "done" to "validation" (line 133). After merge, items should land in validation, not done. This is a one-word change.

Files the agent should NOT touch:

  • hooks/check-board-item.sh -- This gates create_board_item, not update_board_item. Different concern.
  • hooks/boards-config.sh -- No changes needed; the new hook sources it for BOARDS array.
  • hooks/forgejo-helper.sh -- No changes needed; sourced for _load_forgejo_creds and URL construction.
  • skills/ -- The /validate-ticket skill is a separate concern (see Dependencies).

Acceptance Criteria

  • When an agent calls update_board_item(column="done") on an item whose forgejo_issue_url has no matching validation-{issue-num}-* note with pass tag, the hook denies with a message explaining what's missing
  • When a matching validation note with pass tag exists, the hook allows the move to done
  • When update_board_item is called with any column other than done, the hook is a no-op (exit 0)
  • When update_board_item is called with column=done on an item that has no forgejo_issue_url (e.g., note-type items), the hook allows it (fail-open -- only issue-type items require validation)
  • After PR merge, board-item-on-merge.sh moves items to validation column instead of done
  • Hook fails open on network errors (consistent with all other hooks)

Test Expectations

  • Manual test: simulate the hook by piping a JSON payload with tool_input.column=done and a known board item, verify deny output
  • Manual test: create a validation note with pass tag for a test issue, re-run, verify allow (exit 0)
  • Manual test: pipe a JSON payload with tool_input.column=in_progress, verify exit 0 (no-op)
  • Run command: echo '{"tool_input":{"board_slug":"board-pal-e-agency","item_id":123,"column":"done"}}' | bash hooks/gate-validation-done.sh

Constraints

  • Fail-open pattern: All hooks in this repo use trap 'exit 0' ERR -- network failures, missing jq, or API errors must never block agent workflow.
  • pal-e-docs API contract: The hook queries GET /notes?tags=validation,pass and filters results client-side by slug prefix validation-{issue-num}. No new API endpoint needed.
  • Board item lookup: The hook receives board_slug and item_id in tool_input, but NOT the forgejo_issue_url. It must fetch the item first: GET /boards/{board_slug}/items and find the item by ID. This is the same pattern board-item-on-merge.sh uses.
  • Column enum dependency: The validation column must exist in the pal-e-docs DB before board-item-on-merge.sh can target it. Issue #223 (pal-e-api PR #226) adds this column via alembic migration. If that PR is not yet merged, the board-item-on-merge.sh change should be gated behind it or use a fallback. Recommendation: Ship the hook first (it only blocks moves TO done, which works regardless of whether the validation column exists). Ship the board-item-on-merge.sh change separately after #223 merges.
  • No /validate-ticket skill yet: The skill referenced in template-validation (skill-validate-ticket) does not exist. This hook enforces the gate; the skill is the tool for creating validation notes. They are independent -- the hook works without the skill (agents can create validation notes manually). The skill is tracked as discovered scope.

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • pal-e-agency -- project this affects
  • sop-board-workflow -- defines the validation column semantics and done criteria
  • template-validation -- defines the validation note format the hook checks for
  • forgejo_admin/pal-e-api#223 -- adds validation to BoardColumn DB enum (dependency for board-item-on-merge.sh change)
  • hooks/board-item-on-merge.sh -- currently moves to done, needs to move to validation
  • hooks/check-board-item.sh -- sibling pattern (PreToolUse on create_board_item)
  • Discovered scope: /validate-ticket skill (skill-validate-ticket note + skills/validate-ticket/SKILL.md)
### Type Feature ### Lineage Standalone -- discovered while documenting the validation column flow in `sop-board-workflow` and `template-validation`. The right-side gate (validation -> done) mirrors the left-side gate (todo -> next_up via `/review-ticket`), but has no hook enforcement yet. The left gate has `check-board-item.sh`; the right gate has nothing. ### Repo `forgejo_admin/claude-custom` ### User Story As Betty Sue (PM agent) I want the `done` column to reject board items that lack a validation note with PASS verdict So that no ticket can be marked done without production verification, enforcing the DORA Lead Time definition (clock stops at validation PASS, not at merge). ### Context The kanban board has a `validation` column between `needs_approval` and `done` (per `sop-board-workflow`). The `template-validation` note defines the validation note format: slug `validation-{issue-number}-{YYYY-MM-DD}`, tags `validation,pass`, and a PASS/PARTIAL/FAIL verdict. Today, nothing prevents an agent from calling `update_board_item(column="done")` directly, bypassing validation. The `board-item-on-merge.sh` PostToolUse hook already auto-moves items to done after merge -- this needs to either route through the gate or be updated to move to `validation` instead. **Enforcement signals available (researched):** 1. **pal-e-docs API** -- `GET /notes?tags=validation,pass` filtered by issue number in slug. This is the primary signal. Naming convention: `validation-{issue-num}-*`. 2. **Forgejo issue labels** -- `status:approved` label exists (set by `label-on-verdict.sh`). However, this means QA-approved, not production-validated. Not sufficient alone. 3. **Woodpecker pipeline status** -- Could check last pipeline succeeded, but this proves CI passed, not production validation. Not the right signal. 4. **Board item's own `forgejo_issue_url`** -- Needed to extract the issue number, which becomes the validation note lookup key. **Decision: Use signal #1 (pal-e-docs validation note existence with `pass` tag).** This is the only signal that proves someone actually ran the validation checks and recorded evidence. ### File Targets Files the agent should modify or create: - `hooks/gate-validation-done.sh` (NEW) -- PreToolUse hook that intercepts `update_board_item` calls where `column=done`, extracts the issue number from the board item's `forgejo_issue_url`, queries pal-e-docs for a validation note with tag `pass`, and denies if none exists. Follow the pattern in `hooks/check-board-item.sh` (jq parsing, fail-open, deny with reason). - `settings.json` -- Add a new PreToolUse entry with matcher `mcp__pal-e-docs__update_board_item` pointing to `gate-validation-done.sh`. - `hooks/board-item-on-merge.sh` -- Change target column from `"done"` to `"validation"` (line 133). After merge, items should land in validation, not done. This is a one-word change. Files the agent should NOT touch: - `hooks/check-board-item.sh` -- This gates `create_board_item`, not `update_board_item`. Different concern. - `hooks/boards-config.sh` -- No changes needed; the new hook sources it for `BOARDS` array. - `hooks/forgejo-helper.sh` -- No changes needed; sourced for `_load_forgejo_creds` and URL construction. - `skills/` -- The `/validate-ticket` skill is a separate concern (see Dependencies). ### Acceptance Criteria - [ ] When an agent calls `update_board_item(column="done")` on an item whose `forgejo_issue_url` has no matching `validation-{issue-num}-*` note with `pass` tag, the hook denies with a message explaining what's missing - [ ] When a matching validation note with `pass` tag exists, the hook allows the move to done - [ ] When `update_board_item` is called with any column other than `done`, the hook is a no-op (exit 0) - [ ] When `update_board_item` is called with `column=done` on an item that has no `forgejo_issue_url` (e.g., note-type items), the hook allows it (fail-open -- only issue-type items require validation) - [ ] After PR merge, `board-item-on-merge.sh` moves items to `validation` column instead of `done` - [ ] Hook fails open on network errors (consistent with all other hooks) ### Test Expectations - [ ] Manual test: simulate the hook by piping a JSON payload with `tool_input.column=done` and a known board item, verify deny output - [ ] Manual test: create a validation note with `pass` tag for a test issue, re-run, verify allow (exit 0) - [ ] Manual test: pipe a JSON payload with `tool_input.column=in_progress`, verify exit 0 (no-op) - Run command: `echo '{"tool_input":{"board_slug":"board-pal-e-agency","item_id":123,"column":"done"}}' | bash hooks/gate-validation-done.sh` ### Constraints - **Fail-open pattern:** All hooks in this repo use `trap 'exit 0' ERR` -- network failures, missing jq, or API errors must never block agent workflow. - **pal-e-docs API contract:** The hook queries `GET /notes?tags=validation,pass` and filters results client-side by slug prefix `validation-{issue-num}`. No new API endpoint needed. - **Board item lookup:** The hook receives `board_slug` and `item_id` in `tool_input`, but NOT the `forgejo_issue_url`. It must fetch the item first: `GET /boards/{board_slug}/items` and find the item by ID. This is the same pattern `board-item-on-merge.sh` uses. - **Column enum dependency:** The `validation` column must exist in the pal-e-docs DB before `board-item-on-merge.sh` can target it. Issue #223 (pal-e-api PR #226) adds this column via alembic migration. If that PR is not yet merged, the `board-item-on-merge.sh` change should be gated behind it or use a fallback. **Recommendation:** Ship the hook first (it only blocks moves TO done, which works regardless of whether the validation column exists). Ship the `board-item-on-merge.sh` change separately after #223 merges. - **No `/validate-ticket` skill yet:** The skill referenced in `template-validation` (`skill-validate-ticket`) does not exist. This hook enforces the gate; the skill is the tool for creating validation notes. They are independent -- the hook works without the skill (agents can create validation notes manually). The skill is tracked as discovered scope. ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `pal-e-agency` -- project this affects - `sop-board-workflow` -- defines the validation column semantics and done criteria - `template-validation` -- defines the validation note format the hook checks for - `forgejo_admin/pal-e-api#223` -- adds `validation` to BoardColumn DB enum (dependency for board-item-on-merge.sh change) - `hooks/board-item-on-merge.sh` -- currently moves to `done`, needs to move to `validation` - `hooks/check-board-item.sh` -- sibling pattern (PreToolUse on create_board_item) - Discovered scope: `/validate-ticket` skill (skill-validate-ticket note + skills/validate-ticket/SKILL.md)
Author
Contributor

Scope Review: READY

Review note: review-519-2026-03-28
Ticket is fully scoped, traceable, and executable. All file targets verified, all dependencies satisfied.

Minor recommendations (non-blocking):

  • Line reference in File Targets: "line 133" should be "line 132" for board-item-on-merge.sh
  • Settings.json: consider adding the new hook to the existing update_board_item matcher's hooks array (alongside check-board-advance.sh) rather than a separate entry, to stay consistent with the Bash matcher pattern and to cover bulk_move_board_items as well
## Scope Review: READY Review note: `review-519-2026-03-28` Ticket is fully scoped, traceable, and executable. All file targets verified, all dependencies satisfied. Minor recommendations (non-blocking): - Line reference in File Targets: "line 133" should be "line 132" for `board-item-on-merge.sh` - Settings.json: consider adding the new hook to the existing `update_board_item` matcher's hooks array (alongside `check-board-advance.sh`) rather than a separate entry, to stay consistent with the Bash matcher pattern and to cover `bulk_move_board_items` as well
Author
Contributor

Scope Review: READY (todo→next_up gate)

Review note: review-519-2026-03-28-dispatch
Ticket is dispatch-ready. No changes to target files since prior review. All dependencies satisfied (pal-e-api#223 closed, validation column in production). No in-progress conflicts, no existing branches.

Prior review recommendations (line reference off-by-one, settings.json matcher placement) remain as implementation guidance — neither is a blocker.

## Scope Review: READY (todo→next_up gate) Review note: `review-519-2026-03-28-dispatch` Ticket is dispatch-ready. No changes to target files since prior review. All dependencies satisfied (pal-e-api#223 closed, validation column in production). No in-progress conflicts, no existing branches. Prior review recommendations (line reference off-by-one, settings.json matcher placement) remain as implementation guidance — neither is a blocker.
Author
Contributor

Scope Review: READY (todo→next_up dispatch gate)

Review note: review-519-2026-03-28-dispatch
Ticket is dispatch-ready. No changes to target files since prior review (review-519-2026-03-28). All dependencies satisfied (pal-e-api#223 closed, validation column in production). No in-progress conflicts, no existing branches.

Implementation notes for agent:

  • Add new hook to existing update_board_item matcher's hooks array (settings.json lines 194-201) rather than creating a duplicate matcher entry
  • board-item-on-merge.sh target line is 132, not 133 as stated in issue body
## Scope Review: READY (todo→next_up dispatch gate) Review note: `review-519-2026-03-28-dispatch` Ticket is dispatch-ready. No changes to target files since prior review (`review-519-2026-03-28`). All dependencies satisfied (pal-e-api#223 closed, validation column in production). No in-progress conflicts, no existing branches. Implementation notes for agent: - Add new hook to existing `update_board_item` matcher's hooks array (settings.json lines 194-201) rather than creating a duplicate matcher entry - `board-item-on-merge.sh` target line is 132, not 133 as stated in issue body
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/claude-custom#210
No description provided.