refactor(task-board): fence reviewer dispatch on ids, sign reviewer identity - #5813
Merged
Conversation
pedrofrxncx
force-pushed
the
claude/drop-review-claims
branch
from
August 6, 2026 15:29
61e91ca to
52874be
Compare
pedrofrxncx
enabled auto-merge (squash)
August 6, 2026 15:33
pedrofrxncx
force-pushed
the
claude/drop-review-claims
branch
from
August 13, 2026 14:35
7b5c542 to
4dfd425
Compare
…dentity `task_board_review_claims` did two unrelated jobs — idempotent dispatch and reviewer-identity binding — and neither needs a table. Dispatch: the reviewer run's thread id is now derived from (task, reviewer, cycle, attempt), so the `threads` PK is the fence — the racing triggers (60s sweeper, the dialog's 10s poll) collapse on an INSERT … ON CONFLICT DO NOTHING, and the run itself carries the matching deterministic DBOS workflow ID (`review:<item>:<kind>:<cycle>:<attempt>`), the same idiom as `stall-nudge:<item>:<thread>`. `attempt` is the cycle's spent-attempt count, so the retry `MAX_REVIEWER_ATTEMPTS` allows after a dead run gets ids of its own instead of collapsing onto the corpse — the job `releaseReviewerClaim` did. Identity: the `rtok_` token is an HMAC over (task, reviewer, cycle) (`review-token.ts`, modelled on `file-storage/share-password.ts`) instead of a random string looked up in the table. `TASK_BOARD_REVIEW_DECISION` verifies it statelessly and keeps today's semantics exactly — a missing/wrong token still records the decision, just with `verified: false`, so it never counts toward auto-merge, and it is scoped to the CURRENT cycle on both paths so a token kept across a bounce cannot re-approve without a review. Rollout step A: claims are no longer written and `claimReviewer` / `releaseReviewerClaim` are gone, but the decision tool still accepts a legacy claim token so review cycles in flight across the deploy keep counting. Step B (follow-up, after a day): drop the legacy branch, `resolveReviewClaimByToken`, `reviewTokenVerified`, `TaskBoardReviewClaimTable`, and the table.
pedrofrxncx
force-pushed
the
claude/drop-review-claims
branch
from
August 13, 2026 14:36
4dfd425 to
b759ea0
Compare
decocms Bot
pushed a commit
that referenced
this pull request
Aug 13, 2026
PR: #5813 refactor(task-board): fence reviewer dispatch on ids, sign reviewer identity Bump type: patch - decocms (apps/api/package.json): 4.213.0 -> 4.213.1 - @decocms/native (apps/native/package.json): 4.213.0 -> 4.213.1 Deploy-Scope: server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
apps/api/src/tools/task-board/review-claims-removal-spec.md— step A of its two-deploy rollout.What
task_board_review_claimsexisted to do two unrelated jobs. Neither needs a table.1. Idempotent dispatch → deterministic ids. The reviewer run's thread id is now derived from
(task, reviewer, cycleAt.toISOString()), so thethreadsprimary key is the fence: the racing triggers (60s sweeper, the task dialog's 10s poll) collapse on anINSERT … ON CONFLICT DO NOTHING, and the loser returnsisNew: falsebefore anything else happens — no duplicate thread, no duplicatereview_requested, no duplicate run. The run also carries a matching deterministic DBOS workflow ID (review:<item>:<kind>:<cycle>), thestall-nudge:<item>:<thread>idiom.2. Reviewer identity → HMAC. The
rtok_token is now a signature over the same tuple (review-token.ts, modelled onfile-storage/share-password.ts: same signing key, sametimingSafeEqual) instead of a random string resolved through the table.TASK_BOARD_REVIEW_DECISIONverifies it statelessly, deriving the cycle from the existinglastInReviewTimehelper. Semantics are unchanged: a missing/wrong token still records the decision withverified: false, so it never counts towardallEnabledReviewersVerifiedApproved. Fail open on recording, fail closed on merging. The web ship button's deliberate asymmetry is untouched.Deviation from the spec
The spec puts the fence at the DBOS workflow ID alone. That is not sufficient:
enqueueAgentRunForTaskcreates the thread, links it, and records activity beforeenqueueThreadRun, so both racers would still produce a thread and a timeline entry with only the run collapsing. Hence the deterministic thread id as the real fence.For the same reason the compensation the spec deletes could not be deleted outright — a dispatch that throws after the thread exists would leave
reviewerHandledThisCyclereading it as this cycle's reviewer forever. It is now athreads.deleteof the fence thread instead of a claim-row delete.Rollout
claimReviewerandreleaseReviewerClaimdeleted. The decision tool accepts an HMAC match or a legacy claim lookup, so review cycles in flight across the deploy keep counting toward auto-merge. Table still present.resolveReviewClaimByToken,TaskBoardReviewClaimTable, and add the drop migration.Testing
review-token.test.ts: round-trip plus wrong-reviewer / wrong-task / wrong-cycle / tampered / empty rejection.bun test apps/api/src/tools/task-board/{review-token,enqueue-reviewer,review-sweeper}.test.ts— 17 pass. No existing test asserted claim behavior (nothing to invert).bun run check,bun run lint,bun run fmt,knipall clean.Summary by cubic
Fences reviewer dispatch on deterministic thread and workflow IDs keyed by (task, reviewer, cycle, attempt), and replaces claim-table tokens with HMAC-signed
rtok_tokens. This stops duplicate runs and verifies reviewer identity while keeping the “record unverified decisions but exclude them from auto-merge” behavior.threadsPK is the fence; the run uses a matching workflow IDreview:<item>:<kind>:<cycle>:<attempt>.enqueueAgentRunForTasknow acceptsfenceand returnsisNew; on enqueue failure, it deletes the fence thread to avoid a stuck “handled” state.review-token.ts(TASK_BOARD_REVIEW_DECISIONchecks an HMAC over (task, reviewer, current cycle)); unverified decisions still record but don’t count toward auto-merge. Falls back to legacy claim lookup for cycles dispatched before this change.Rollout
claimReviewer/releaseReviewerClaim; mint HMAC tokens; accept legacy claim tokens for in-flight cycles.resolveReviewClaimByToken, and droptask_board_review_claimswith its migration.Written for commit b759ea0. Summary will update on new commits.