opencode-bot: changes for issue #192 #201
No reviewers
Labels
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!201
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "opencode/issue-192"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #192.
🤖 Reviewing this PR — the review will link the full session transcript.
@claude-reviewer
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
This PR moves the deployment-wide admin role onto per-board grants (
BoardMember.Role), introducesBoardPermissionsas the single gate for running a board (rename/columns/tags/access/delete), and reworksBoardViewer/BoardAccessso a viewer'sIsAdminis now a per-board fact resolved from grant rows rather than the allowlist snapshot. I checked the full PR branch (not just the truncated diff) for the parts that are easy to get wrong in a change like this, and it holds up:MemberRoleenum or oldBoardViewer.For(user, allowlist)overload anywhere in the tree.BoardService.SetAccessAsynccomputes the last-admin refusal against the post-save state and throws beforeSaveChangesAsync, so a rejected save can't partially commit — and it correctly allows "remove last admin + add a new one" in one save, as documented.MemberService.RemoveAsynchas its own last-board-admin guard (a member can be the last admin of a board without being a super-admin), independent of the access-page guard.DatabaseInitializer.EnsureBoardAdminsAsyncruns after seeding admins/default board, only touches genuinely admin-less boards, and is idempotent.Members.Role/BoardMembercolumns (consistent with them being unencrypted by design) and upserts withON CONFLICT DO UPDATEagainst the(BoardId, MemberDid)PK, correctly promoting existing plain grants rather than skipping them.admin.access.admin,admin.access.lastadmin,admin.error.notboardadmin,admin.members.remove.lastadmin, etc.) are present in bothenanddedictionaries inLocaleService.cs, and stale keys (admin.members.role*,admin.boards.error.duplicate,admin.access.admin.note) were removed from both.ColumnService/TagService/BoardServicemutators consistently take aBoardViewer actorand callBoardPermissions.RequireAdministerbefore writing.docs/(auth.md, boards-and-permissions.md, data.md, orientation.md) andChangelog.cs(both locales) were updated in the same PR, matching the repo's own convention.BoardPermissionsTestscover the sharp edge explicitly (a super-admin is not automatically an admin of a given board).I didn't find any correctness or security issues worth blocking on.
⏱ 2m 0s · 15 turns · tokens: 94.6k in / 9.1k out (+1434.9k cache-read)
📜 Full session transcript
🛑 Verdict: request changes — blocking issues below.
The per-board roles rework is well thought through —
BoardPermissionsis enforced in every mutator, the last-admin refusal is computed against the post-save state, the migration backfills in SQL with the rightON CONFLICTsemantics, and the no-admin-bypass change toBoardAccess.VisibleTois consistently applied (admin pages keep their access-ignored reads, member-facing paths all build viewers viaViewerOnAsync). Locale keys for the new refusals exist in both languages, and the unit tests were updated to the newBoardViewershape.One blocker: the PR removes
Rolefrom theMemberentity while leaving the column in the database — but that column was created byInitialSchemaasinteger NOT NULLwith no default. EF will now omit it from everyINSERT INTO "Members", and Postgres will reject those inserts with a not-null violation. That breaksDatabaseInitializer.SeedAdminsAsync(a fresh database fails at startup, and any deployment where a configured admin lacks a row fails too),/signup, the admin members page and the access-request approval — i.e. every way a member is added. The column needs either aSET DEFAULT 0in theAddBoardRolesmigration, or to stay mapped on the entity (unread) until the later drop.Minor, non-blocking:
Boards.razorCreateAsyncpasses the current DID, which is null-safe in the service but would create an admin-less board until the next restart'sEnsureBoardAdminsAsyncrepair if the claim were ever missing; andAddBoardRoles.Down()dropsBoardMembers.Rolewithout writing post-deploy promotions back toMembers.Role, so a rollback loses admins promoted after deploy — consistent with the documented trade-off, just worth being aware of.⏱ 3m 22s · 15 steps · $0.0630 · tokens: 233.2k in / 14.9k out (+686.7k cache-read) · Go: 5h 41% · wk 41% · mo 22%
📜 Full session transcript
@ -37,3 +31,3 @@public bool UsesLocalProfile { get; set; }public MemberRole Role { get; set; } = MemberRole.Member;// Members.Role is deliberately gone from the entity but still in theUnmapping
Members.Rolewhile the column staysinteger NOT NULLwith no database default (seeInitialSchema, line 36 — nodefaultValue) breaks every member insert: EF omits the column from the INSERT and Postgres raises a not-null violation. This hitsDatabaseInitializer.SeedAdminsAsync(fresh-DB startup fails),/signup, the admin members page and access-request approval. Either keep the property mapped-but-unread for this release, or addALTER TABLE "Members" ALTER COLUMN "Role" SET DEFAULT 0to theAddBoardRolesmigration.🤖 On it — the reply will link the full session transcript.
What I did
The review's blocker was correct, and I fixed it on
opencode/issue-192(commit59385b4).The blocker —
Members.Roleunmapped butNOT NULLwith no default. Verified againstInitialSchema(line 36:Role = table.Column<int>(..., nullable: false)— no default) and the new model snapshot, which no longer maps the column. Every EFINSERT INTO "Members"would omit it and Postgres would reject it, breaking admin seeding,/signup, member adds and access-request approval.Fix in
AddBoardRoles:ALTER TABLE "Members" ALTER COLUMN "Role" SET DEFAULT 0— the inert value, since the real role now lives per board onBoardMembersand the super-admin comes from the allowlist. Comment explains why unmapping alone wasn't enough.ALTER COLUMN "Role" DROP DEFAULT, restoring the column to exactly the stateInitialSchemacreated, since the build being rolled back to maps the column and writes it explicitly.Verification (real Postgres 15, local cluster): I wrote a temporary test that ran the full migration chain against a fresh database and then inserted a
Membervia EF — the exact broken path. Without the fix it fails with23502: null value in column "Role" of relation "Members" violates not-null constraint; with the fix it passes andinformation_schemaconfirms the default is0. I then removed the temporary test (it needs a live Postgres, so it doesn't belong in the suite) and ran the full test suite: 482/482 passed, build clean.The two minor findings — deliberately not changed:
Boards.razorCreateAsyncwith a missing DID claim: the service is null-safe andEnsureBoardAdminsAsyncrepairs an admin-less board on the next restart; the claim is on an admin-only page, so I left it.Down()not writing post-deploy promotions back toMembers.Role: the reviewer themselves noted this is consistent with the documented rollback trade-off.No follow-up issues filed — everything actionable was either fixed here or already documented.
⏱ 3m 14s · 39 steps · $0.0399 · tokens: 112.6k in / 5.9k out (+669.7k cache-read) · Go: 5h 42% · wk 41% · mo 23%
📜 Full session transcript
🤖 Reviewing this PR — the review will link the full session transcript.
✅ Verdict: approve — no blocking issues found.
Re-review: the blocker from the previous pass is fixed. The
AddBoardRolesmigration now runsALTER TABLE "Members" ALTER COLUMN "Role" SET DEFAULT 0, so the unmappedNOT NULLcolumn no longer breaks member inserts (seeding,/signup, the members page, access-request approval);Down()restores the original no-default state, which is correct since the rolled-back build maps and writes the column. The migration comments document the reasoning well.Everything else from the previous pass still holds: no leftover
MemberRolereferences, no staleDuplicateBoardNameExceptionorBoardViewer.For(user, allowlist)call sites,BoardPermissions.RequireAdministerenforced in every board mutator, the last-admin refusal computed against the post-save state, and the new refusal strings present in both locales.Remaining minor, non-blocking notes (unchanged from last pass):
AddBoardRoles.Down()dropsBoardMembers.Rolewithout writing post-deploy promotions back toMembers.Role, so a rollback loses admins promoted after deploy — a documented trade-off; andBoards.razorCreateAsyncpasses the current DID, which is null-safe in the service but would create an admin-less board until the next restart'sEnsureBoardAdminsAsyncrepair if the claim were ever missing.⏱ 0m 25s · 4 steps · $0.0107 · tokens: 42.5k in / 1.3k out (+121.3k cache-read) · Go: 5h 42% · wk 41% · mo 23%
📜 Full session transcript