|
| 1 | +# review-checklist |
| 2 | + |
| 3 | +Pre-merge verification checklist for connector code reviews. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Quick Scan (30 seconds) |
| 8 | + |
| 9 | +Run these checks first to catch obvious issues: |
| 10 | + |
| 11 | +- [ ] **No secrets in code** - Search for `password`, `secret`, `token`, `key` literals |
| 12 | +- [ ] **Error prefix present** - All errors start with `baton-{service}:` |
| 13 | +- [ ] **Defer after error check** - `defer resp.Body.Close()` never before `if err != nil` |
| 14 | +- [ ] **Context passed through** - Every API call receives `ctx` |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## ResourceSyncer Check |
| 19 | + |
| 20 | +For each resource type (users, groups, roles): |
| 21 | + |
| 22 | +- [ ] Implements `ResourceSyncer` interface correctly |
| 23 | +- [ ] Registered with `connectorbuilder.WithResourceSyncers()` |
| 24 | +- [ ] Returns `ResourceType()` with correct traits |
| 25 | +- [ ] `List()` handles empty results without error |
| 26 | +- [ ] `Entitlements()` returns at least one entitlement for membership resources |
| 27 | +- [ ] `Grants()` returns grants linking principals to entitlements |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Pagination Check |
| 32 | + |
| 33 | +**Termination:** |
| 34 | +- [ ] Returns `""` (empty string) when no more pages |
| 35 | +- [ ] Does NOT return `""` when there ARE more pages |
| 36 | +- [ ] Handles API returning fewer items than requested (not necessarily end) |
| 37 | + |
| 38 | +**Token handling:** |
| 39 | +- [ ] Uses `pagination.Bag` for multi-resource pagination |
| 40 | +- [ ] Extracts token correctly from incoming `*pagination.Token` |
| 41 | +- [ ] Sets next token on outgoing bag |
| 42 | + |
| 43 | +**Red flag patterns:** |
| 44 | +```go |
| 45 | +// WRONG - always terminates after first page |
| 46 | +return resources, "", nil, nil |
| 47 | + |
| 48 | +// WRONG - infinite loop |
| 49 | +return resources, "next", nil, nil // hardcoded token |
| 50 | + |
| 51 | +// CORRECT - check API response |
| 52 | +if resp.NextPage == "" { |
| 53 | + return resources, "", nil, nil |
| 54 | +} |
| 55 | +return resources, resp.NextPage, nil, nil |
| 56 | +``` |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## HTTP Safety Check |
| 61 | + |
| 62 | +**Nil pointer safety:** |
| 63 | +- [ ] `resp.StatusCode` never accessed when `err != nil` without nil check |
| 64 | +- [ ] `resp.Body` never accessed when `err != nil` without nil check |
| 65 | +- [ ] `defer resp.Body.Close()` placed AFTER error check |
| 66 | + |
| 67 | +**Type assertions:** |
| 68 | +- [ ] All type assertions use two-value form: `x, ok := val.(Type)` |
| 69 | +- [ ] Direct assertions `val.(Type)` flagged as potential panic |
| 70 | + |
| 71 | +**ParentResourceId:** |
| 72 | +- [ ] `resource.ParentResourceId.Resource` has nil check first |
| 73 | +- [ ] Or uses helper: `if resource.ParentResourceId != nil { ... }` |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## Grant/Revoke Check (if provisioning) |
| 78 | + |
| 79 | +**Entity sources (CRITICAL - #1 bug pattern):** |
| 80 | +- [ ] User/account ID comes from `principal.Id.Resource` |
| 81 | +- [ ] Context (workspace/org) comes from `principal.ParentResourceId.Resource` |
| 82 | +- [ ] Role/group ID comes from `entitlement.Resource.Id.Resource` |
| 83 | +- [ ] NO use of `entitlement.Resource.ParentResourceId` for context |
| 84 | + |
| 85 | +**Idempotency:** |
| 86 | +- [ ] Grant handles "already exists" as success |
| 87 | +- [ ] Revoke handles "not found" as success |
| 88 | + |
| 89 | +**Revoke specifically:** |
| 90 | +- [ ] Uses `grant.Principal` for principal info |
| 91 | +- [ ] Uses `grant.Entitlement` for entitlement info |
| 92 | +- [ ] Context still from `grant.Principal.ParentResourceId` |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## Error Handling Check |
| 97 | + |
| 98 | +**Wrapping:** |
| 99 | +- [ ] All errors use `%w` not `%v` for wrapping |
| 100 | +- [ ] All errors include connector prefix: `baton-{service}:` |
| 101 | +- [ ] Error messages include relevant IDs (user, resource, page) |
| 102 | + |
| 103 | +**No swallowing:** |
| 104 | +- [ ] No `log.Println(err)` followed by continuing |
| 105 | +- [ ] All errors either returned or explicitly handled |
| 106 | +- [ ] No empty `if err != nil { }` blocks |
| 107 | + |
| 108 | +**Context cancellation:** |
| 109 | +- [ ] Long loops check `ctx.Done()` periodically |
| 110 | +- [ ] API calls receive context |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## ID Stability Check |
| 115 | + |
| 116 | +**ResourceId requirements:** |
| 117 | +- [ ] IDs are stable across syncs (same user = same ID) |
| 118 | +- [ ] IDs don't change when resource is renamed |
| 119 | +- [ ] IDs are unique within resource type |
| 120 | +- [ ] IDs are deterministic (no random components) |
| 121 | + |
| 122 | +**Common mistakes:** |
| 123 | +```go |
| 124 | +// WRONG - email can change |
| 125 | +rs.NewUserResource(user.Name, userType, user.Email, ...) |
| 126 | + |
| 127 | +// CORRECT - use stable ID |
| 128 | +rs.NewUserResource(user.Name, userType, user.ID, ...) |
| 129 | +``` |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Configuration Check |
| 134 | + |
| 135 | +- [ ] Required fields marked with `field.WithRequired(true)` |
| 136 | +- [ ] Secrets marked with `field.WithIsSecret(true)` |
| 137 | +- [ ] Default values sensible |
| 138 | +- [ ] Environment variable naming follows `BATON_` convention |
| 139 | +- [ ] No hardcoded credentials or URLs |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +## Testing Indicators |
| 144 | + |
| 145 | +Look for: |
| 146 | +- [ ] `_test.go` files exist for non-trivial logic |
| 147 | +- [ ] Mock server or API fixtures for integration tests |
| 148 | +- [ ] Testability flags (`--base-url`, `--insecure`) in config |
| 149 | +- [ ] No tests that require real credentials |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## Documentation Check |
| 154 | + |
| 155 | +- [ ] README includes required environment variables |
| 156 | +- [ ] README shows example usage |
| 157 | +- [ ] Complex logic has comments explaining WHY (not what) |
| 158 | +- [ ] No TODO comments for critical functionality |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Final Sanity Check |
| 163 | + |
| 164 | +Ask yourself: |
| 165 | +1. "If this runs against production, what's the worst that happens?" |
| 166 | +2. "If an API call fails, will we know what failed and why?" |
| 167 | +3. "If pagination breaks, will it infinite loop or stop early?" |
| 168 | +4. "If Grant/Revoke runs, will it affect the right user in the right context?" |
| 169 | + |
| 170 | +--- |
| 171 | + |
| 172 | +## Review Comment Templates |
| 173 | + |
| 174 | +**For entity source bugs:** |
| 175 | +``` |
| 176 | +This gets the workspace from `entitlement.Resource.ParentResourceId` but it should come from `principal.ParentResourceId`. The principal's context determines where the grant happens. |
| 177 | +``` |
| 178 | + |
| 179 | +**For pagination bugs:** |
| 180 | +``` |
| 181 | +This always returns an empty next token, which will stop pagination after the first page. The next token should come from the API response. |
| 182 | +``` |
| 183 | + |
| 184 | +**For nil pointer risks:** |
| 185 | +``` |
| 186 | +If the HTTP request fails, `resp` may be nil. This will panic when accessing `resp.StatusCode`. Add a nil check before accessing response fields in error paths. |
| 187 | +``` |
| 188 | + |
| 189 | +**For error swallowing:** |
| 190 | +``` |
| 191 | +This logs the error but continues execution. If this fails, the sync will report success with incomplete data. Return the error instead. |
| 192 | +``` |
0 commit comments