From 01fa5c3ca9bd1f15d9317750c74ade08279577ad Mon Sep 17 00:00:00 2001 From: Rassl Date: Wed, 2 Sep 2026 03:21:50 +0400 Subject: [PATCH 1/2] feat: send decided_by: admin on review approve/dismiss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marks admin-UI decisions on the review node so they are distinguishable from workflow-made ones (workflows send their own identity, e.g. "stakwork"). Plain body field — boltwall rewrites headers, so the old X-Decided-By channel arrived blanked. --- src/lib/graph-api.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/graph-api.ts b/src/lib/graph-api.ts index 3e3154f..a0f2a46 100644 --- a/src/lib/graph-api.ts +++ b/src/lib/graph-api.ts @@ -1178,7 +1178,12 @@ export async function approveReview( // cancel_active_runs: an admin deciding here makes any in-flight // human-review workflow moot, so ask the backend to stop it. Workflows // deciding their own reviews omit this — cancelling would kill their run. - const body: Record = { cancel_active_runs: true } + // decided_by distinguishes admin-UI decisions from workflow ones on the + // review node (workflows send their own identity, e.g. "stakwork"). + const body: Record = { + cancel_active_runs: true, + decided_by: "admin", + } if (overridePayload) body.override_payload = overridePayload return api.post( `/v2/reviews/${refId}/approve`, @@ -1204,10 +1209,11 @@ export async function dismissReview( } return { status: "Success", review } } - // cancel_active_runs: same admin-decided semantics as approveReview above. + // cancel_active_runs + decided_by: same admin-decided semantics as + // approveReview above. return api.post( `/v2/reviews/${refId}/dismiss`, - { reason, cancel_active_runs: true }, + { reason, cancel_active_runs: true, decided_by: "admin" }, undefined, signal ) From 9687c9b599b01e82be56d90a7b3f053f8e9f1fc9 Mon Sep 17 00:00:00 2001 From: Rassl Date: Wed, 2 Sep 2026 04:09:38 +0400 Subject: [PATCH 2/2] feat: decided_by for review nodes --- src/components/admin/review-row.tsx | 10 +++++++++ src/lib/__tests__/reviews.test.tsx | 35 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/components/admin/review-row.tsx b/src/components/admin/review-row.tsx index 0da9f62..155e131 100644 --- a/src/components/admin/review-row.tsx +++ b/src/components/admin/review-row.tsx @@ -644,6 +644,10 @@ export function ReviewRow({ // time (decided_at is unset until approve/dismiss). const rowTimestamp = review.decided_at ?? review.created_at const relativeTime = formatDateRelative(rowTimestamp, rowTimestamp ?? "") + // Who made the decision ("admin" from the UI, "stakwork" from workflows). + // Trim guards legacy reviews decided before decided_by was reliable (""). + const decider = + review.status !== "pending" ? review.decided_by?.trim() : undefined const direction = useMemo( () => extractDirection(review.action_name, review.action_payload), @@ -851,6 +855,12 @@ export function ReviewRow({ {review.run_ref_id && Run #{review.run_ref_id.slice(-5)}} {review.run_ref_id && ·} {relativeTime} + {decider && ( + <> + · + by {decider} + + )}
e.stopPropagation()}> diff --git a/src/lib/__tests__/reviews.test.tsx b/src/lib/__tests__/reviews.test.tsx index 730534d..e936dd6 100644 --- a/src/lib/__tests__/reviews.test.tsx +++ b/src/lib/__tests__/reviews.test.tsx @@ -139,6 +139,41 @@ describe("ReviewRow", () => { expect(badge!.className).toContain("bg-red-500") }) + // ── Decider attribution ───────────────────────────────────────────────────── + + it("shows who decided on decided reviews", () => { + const { container } = render( + + ) + const decider = container.querySelector("[data-testid='review-decider']") + expect(decider).toBeTruthy() + expect(decider!.textContent).toBe("by stakwork") + }) + + it("hides decider on pending reviews and on legacy blank decided_by", () => { + const pending = render( + + ) + expect(pending.container.querySelector("[data-testid='review-decider']")).toBeNull() + + const legacy = render( + + ) + expect(legacy.container.querySelector("[data-testid='review-decider']")).toBeNull() + }) + // ── Approve / Dismiss only for pending ───────────────────────────────────── it("shows Approve and Dismiss buttons only for pending rows", () => {