Estimate: 60m · Depends on: 6.12.3, 6.11.3
The two remaining public-viewer writes — UPVOTE and COMMENT — over the 6.12.3 PublicRequestVote model + the 6.11.3 queue. Per 6.12.2:
PublicRequestVote(workItemId, userId) for the signed-in account, gated by 6.12.3’s canUpvotePublicRequest (any authed account on a public project) — NOT canEdit. The unique (workItemId, userId) makes a second upvote a no-op / toggle (never a double count); the vote COUNT becomes a sort key the 6.11.3 triage queue reads (so the project admin sees the highest-demand requests first — the demand signal). Lock-before-read-derived-update where a concurrent vote could race the count.canCommentPublicRequest — NOT canEdit. These PUBLIC-REQUEST comments are visible on the public surface (distinct from the work item’s INTERNAL comments, which the 6.12.4 projection hides — 6.12.2 fixes that line). Reuse the existing comment model/service where the request is a work_item with a comment thread; mark the public-request comments as public-visible.Stay 4-layer: routes parse + call one service method; the vote/comment writes own their transaction; the count sort threads into the 6.11.3 queue read.
(workItemId, userId) holds, no double count); gated by canUpvotePublicRequest, NOT canEdit.canCommentPublicRequest, NOT canEdit; the work item’s internal comments remain hidden by the 6.12.4 projection.workItemsService; no raw Prisma in routes).PublicRequestVote model + canUpvotePublicRequest / canCommentPublicRequest grants.scripts/plan-seed/data/story-6.11.ts § 6.11.3 — the triage queue the vote-count sort feeds.motir-core/lib/services/workItemsService.ts + the existing comment model/service — the comment thread reused.motir-core/CLAUDE.md § 4-layer + the lock-before-read-derived-update rule.