Skip to content

Commit 53234f9

Browse files
julianknutsenclaude
andcommitted
feat: add admin list for approve flow UI access
Allow julianknutsen, steveyegge, and csells to accept, reject, and close any wanted item as if they were the poster. Self-accept guard still applies. Delete remains poster-only. Also removes unused parseCSVLine to fix lint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8e95ed3 commit 53234f9

3 files changed

Lines changed: 56 additions & 34 deletions

File tree

internal/commons/commons.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -460,35 +460,6 @@ func parseSimpleCSV(data string) []map[string]string {
460460
return result
461461
}
462462

463-
// parseCSVLine parses a single CSV line, handling quoted fields.
464-
func parseCSVLine(line string) []string {
465-
var fields []string
466-
var field strings.Builder
467-
inQuote := false
468-
469-
for i := 0; i < len(line); i++ {
470-
ch := line[i]
471-
switch {
472-
case ch == '"' && !inQuote:
473-
inQuote = true
474-
case ch == '"' && inQuote:
475-
if i+1 < len(line) && line[i+1] == '"' {
476-
field.WriteByte('"')
477-
i++
478-
} else {
479-
inQuote = false
480-
}
481-
case ch == ',' && !inQuote:
482-
fields = append(fields, field.String())
483-
field.Reset()
484-
default:
485-
field.WriteByte(ch)
486-
}
487-
}
488-
fields = append(fields, field.String())
489-
return fields
490-
}
491-
492463
// QueryCompletion fetches the completion record for a wanted item.
493464
func QueryCompletion(db DB, wantedID string) (*CompletionRecord, error) {
494465
return queryCompletionRef(db, wantedID, "")

internal/commons/lifecycle.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,26 +134,36 @@ func PushOriginMain(dbDir string, stdout io.Writer) error {
134134
return PushBranchToRemoteForce(dbDir, "origin", "main", true, stdout)
135135
}
136136

137+
// Admins is the set of rig handles with elevated transition permissions.
138+
// Admins can accept, reject, and close any item as if they were the poster.
139+
var Admins = map[string]bool{
140+
"julianknutsen": true,
141+
"steveyegge": true,
142+
"csells": true,
143+
}
144+
137145
// CanPerformTransition checks whether actor can perform transition t on item.
138146
func CanPerformTransition(item *WantedItem, t Transition, actor string) bool {
139147
if item == nil {
140148
return false
141149
}
150+
isPoster := item.PostedBy == actor
151+
isAdmin := Admins[actor]
142152
switch t {
143153
case TransitionClaim:
144154
return true // any rig can claim
145155
case TransitionUnclaim:
146-
return item.ClaimedBy == actor || item.PostedBy == actor
156+
return item.ClaimedBy == actor || isPoster
147157
case TransitionDone:
148158
return item.ClaimedBy == actor
149159
case TransitionAccept:
150-
return item.PostedBy == actor && item.ClaimedBy != actor
160+
return (isPoster || isAdmin) && item.ClaimedBy != actor
151161
case TransitionReject:
152-
return item.PostedBy == actor
162+
return isPoster || isAdmin
153163
case TransitionClose:
154-
return item.PostedBy == actor
164+
return isPoster || isAdmin
155165
case TransitionDelete:
156-
return item.PostedBy == actor
166+
return isPoster
157167
default:
158168
return false
159169
}

internal/commons/lifecycle_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,47 @@ func TestCanPerformTransition_Accept(t *testing.T) {
126126
}
127127
}
128128

129+
func TestCanPerformTransition_Admin(t *testing.T) {
130+
item := &WantedItem{
131+
ID: "w-test",
132+
Status: "in_review",
133+
PostedBy: "poster",
134+
ClaimedBy: "claimer",
135+
}
136+
137+
// Admin (not claimer) can accept.
138+
if !CanPerformTransition(item, TransitionAccept, "julianknutsen") {
139+
t.Error("admin should be able to accept")
140+
}
141+
// Admin can reject.
142+
if !CanPerformTransition(item, TransitionReject, "julianknutsen") {
143+
t.Error("admin should be able to reject")
144+
}
145+
// Admin can close.
146+
if !CanPerformTransition(item, TransitionClose, "steveyegge") {
147+
t.Error("admin should be able to close")
148+
}
149+
// Admin who is also the claimer cannot accept own work.
150+
selfItem := &WantedItem{
151+
ID: "w-test",
152+
Status: "in_review",
153+
PostedBy: "poster",
154+
ClaimedBy: "csells",
155+
}
156+
if CanPerformTransition(selfItem, TransitionAccept, "csells") {
157+
t.Error("admin who claimed should not be able to accept own work")
158+
}
159+
// Admin cannot delete (poster-only).
160+
openItem := &WantedItem{
161+
ID: "w-test",
162+
Status: "open",
163+
PostedBy: "poster",
164+
}
165+
if CanPerformTransition(openItem, TransitionDelete, "julianknutsen") {
166+
t.Error("admin should not be able to delete (poster-only)")
167+
}
168+
}
169+
129170
func TestTransitionLabel(t *testing.T) {
130171
tests := []struct {
131172
t Transition

0 commit comments

Comments
 (0)