Skip to content

fix(kanban): first-class review lifecycle — salvage of #75451 with review fixes - #83412

Merged
teknium1 merged 19 commits into
mainfrom
salvage-75451-review-lifecycle
Aug 10, 2026
Merged

teknium1 merged 19 commits into
mainfrom
salvage-75451-review-lifecycle

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

Salvages PR #75451's first-class kanban review lifecycle onto current main with five review-identified fixes on top: same-card coder⇄reviewer loops now work end-to-end (request_review → reviewer profile → kanban_complete / kanban_request_changes → same card back to the implementer, provenance preserved), closing the deterministic review-required: dependency deadlock.

Consolidates the community review-loop cluster: base work by @frizikk (#75451), building on @capybara-x / Nikita Barkov (#72554) and @kaishi00 (#49368) — all 14 contributor commits preserved via cherry-pick.

Changes

  • Contributor work (14 commits, authorship preserved): review status transitions with CAS + provenance, reviewer dispatch with per-profile caps, review-phase preservation across crash/TTL/timeout recovery, secret redaction at the domain boundary, graph-aware legacy-deadlock diagnostics, dashboard review column, sdlc-review bundled skill (environments-gated to kanban), ~1,700 lines of lifecycle tests, en+zh-Hans docs
  • Our fixes on top (5 commits):
    • write_txn nesting is explicit opt-in again (allow_nested=True); plain nesting raises like main always did; swarm root activation inlined so complete_task side effects can't fire under a rollback-able outer txn
    • review-lane dispatch skips the active_pr/recent_success respawn-guard rules (an existing PR is the precondition for review, not a dupe signal); rate-limit cooldown still applies
    • request_review refuses to yank a live worker's claim without run-id proof or --force; malformed re-review provenance gets a distinct failure reason surfaced through tool/CLI
    • consecutive_failures preserved (not reset) across review round-trips — crash-looping implementers still trip the breaker
    • descendant invalidation on ancestor reopen moved into kanban_db (single implementation, all surfaces), non-silent (descendant_invalidated events + comments), kills routed through the reclaim helper post-commit

Validation

Result
Targeted kanban suites (lifecycle, complete, surfaces, swarm, notify, dashboard) 83 passed, 0 failed
Regression tests added per fix nesting rollback, review-lane guard, claim theft, counter cycle, invalidation parity
Stale-base gate rebased, 0 behind origin/main
Attribution audit all emails mapped

Supersedes (to close with credit after merge): #49368, #43060, #54849, #81217, #79061, #76892, #71926, #67826; rejected direction: #68210. Fixes #67963.

Infographic

Kanban review lifecycle

nikitaBarkov and others added 19 commits August 10, 2026 12:38
Add a non-terminal "review" status so a worker that finished implementation
can hand off for human review without abusing kanban_block. The old
kanban_block(reason="review-required: ...") convention routed the handoff
through the unblock-loop breaker, so a normal review -> changes -> review
cycle was falsely escalated to triage.

- kanban_db: request_review (running/ready -> review, non-block, emits
  review_requested), reopen_review_task (review -> ready/todo, review_reopened),
  complete_task accepts review -> done, and a review_dispatch gate (default off,
  shared by the dispatcher loop and the gateway health probe).
- kanban_request_review worker tool + `request-review` / `reopen-review` CLI
  verbs; tool wired through toolsets, EXPOSED_TOOLS, _POLISHED_TOOLS.
- Gateway notifier wakes the origin subscriber on review_requested and
  block_loop_detected; the subscription survives until done/archived, so every
  review cycle re-notifies.
- Dashboard PATCH + bulk route the review transitions (request_review /
  reopen_review_task) and render the review column.
- goals.py goal-loop and KANBAN_GUIDANCE recognize review as a terminator.
- Docs (reference tables, user guide, AGENTS.md, zh-Hans mirrors) + tests.

needs_input / failed are unchanged: they still route through kanban_block,
still count toward block_recurrences, and still escalate to triage.
Close the autonomous implement-review-rework loop, preserve parent gating and implementer provenance, distinguish downstream review cards, and surface legacy review dependency deadlocks immediately.

Co-authored-by: kaishi00 <6590895+kaishi00@users.noreply.github.com>
Plain write_txn raises loudly on nesting again (the historical main
invariant); composition primitives (create_task, add_comment) opt in
with allow_nested=True for savepoint semantics. create_swarm activates
the swarm root with an inline blocked->done CAS flip + synthesized run
+ event instead of nesting complete_task, so complete_task's post-commit
side effects (workspace cleanup, failure-counter clear, recompute_ready)
can no longer fire under an open outer transaction; recompute_ready now
runs after the outer commit. recompute_ready docstring corrected.

Regression: plain nesting raises; allow_nested composes and an outer
rollback discards inner work with no side effects fired.
Thread lane= into check_respawn_guard. For review-lane dispatch the
active_pr and recent_success rules are skipped: a fresh PR URL comment
(and often a recent completed run) is the precondition of the canonical
review handoff, not a duplicate-work signal. Rate-limit cooldown and
the auth-blocker check still apply in every lane.

Regression: a review task with a <24h PR comment is spawned by dispatch
while a ready-lane task with the same comment stays deferred; a
rate_limited latest run still defers the review lane.
request_review on a running task under a live claim now requires the
caller to prove ownership (expected_run_id, the unchanged worker path)
or pass an explicit force=True override (CLI --force; dashboard human
actions pass force=True) instead of silently clearing claim_lock /
worker_pid of a live run.

Failures now carry distinct diagnostic reasons via with_reason=True
(mirroring request_changes' tuple pattern): live-claim refusal,
malformed re-review provenance, unsatisfied parents, unknown task, and
CAS miss. Tool/CLI handlers surface the specific reason instead of the
generic 'unknown id or not in running/ready'.

Regression tests: live-claim refusal + force/worker paths; malformed
provenance gets a distinct reason and explicit reviewer= recovers.
request_changes and reopen_review_task no longer reset
consecutive_failures (and last_failure_error) to 0 — review transitions
are neither success nor failure signals, so the circuit-breaker counter
is preserved (not incremented either), mirroring unblock_task (#35072).
Only complete_task's success path clears the counter.

Regression: counter=1 survives a full request_review -> request_changes
-> re-request cycle; a crash after request_changes accumulates to 2 and
trips a failure_limit=2 breaker; complete_task still resets to 0.
…n-silent

Ancestor-reopen descendant invalidation previously lived only in the
dashboard plugin (_set_status_direct), so board semantics diverged by
surface and the retraction was silent: completed work snapped back to
todo and live workers were killed with no operator-visible signal.

Move it into kanban_db.invalidate_descendants_for_parent_reopen as THE
single domain implementation (recursive-CTE discovery and per-run
_retry_status_for_run handling preserved). It composes under a caller's
open transaction via write_txn(allow_nested=True) — the ancestor flip
and the descendant retractions must commit atomically — and opens its
own transaction standalone. The dashboard shim now delegates; the CLI
deliberately has no done-reopen verb (reopen-review is review-phase
only), so the DB-layer function being the single implementation is the
fix, documented in its docstring.

Non-silent: every invalidated descendant gets a descendant_invalidated
event ({ancestor, prior_status, new_status, resume_status}), the legacy
status event for existing live-feed consumers, and a task comment
naming the reopened ancestor. Running descendants keep the termination
behavior (a child building on a retracted premise is wasted spend), but
the events/comment are committed BEFORE the kill, which routes through
_terminate_reclaimed_worker — the same helper the reclaim paths use.

consecutive_failures resets to 0 on invalidated descendants: operator-
initiated invalidation is a deliberate fresh start, deliberately the
opposite of the review-loop rule (reopen_review_task preserves the
counter, #35072) so the autonomous review loop can't launder its own
failure streak.

Regression: DB-function reopen demotes done descendants with events +
comments; running descendant's audit trail is durable before its worker
dies; counter resets; dashboard and DB paths produce identical task
states, event kinds, and comment counts.
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 647f2e9 — fix(kanban): move descendant invalidation to domain layer, m

⚠️ Warnings

OSV vulnerability scan · View job

2 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 3m11s vs 7m30s (-57.6%). 12 job(s) slower, 13 faster, 1 unchanged.

  • Python tests / Run tests slice 10/12: -54.0s
  • Detect affected areas: -35.0s
  • Python tests / Run tests slice 2/12: -32.0s
  • Python lints / Windows footguns (blocking): -31.0s
  • Python tests / Run tests slice 7/12: -18.0s

@webdevfrancisco

Copy link
Copy Markdown
Contributor

@teknium1 I'd built a local version of this just before it landed. Yours is better — checking source_status + run_id is a real guarantee; mine was just reading a verdict out of a comment.
One issue I hit: I also had a skill named sdlc-review in a different category folder. After this landed, every review claim started failing with Error: Unknown skill(s): sdlc-review. The logs correctly showed a skill name collision (2 candidates), but the error message itself said "unknown," so I spent time looking for a missing file that was already present. Deleting my version fixed it. It might be worth surfacing the collision more clearly in the error message.
One question: the skill selects its lens by counting prior changes_requested to determine the round, but I couldn't find any limit on how many rounds are allowed. Is that intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dependency-gate semantics gap: blocked parents deadlock dependency children

5 participants