Estimate: 55m · Depends on: 6.12.5, 6.12.6
Lock the load-bearing guarantees: (1) public access enforces cross-org READ but blocks every write except the three grants, (2) duplicate detection matches, and (3) voting is one-per-account. On a real Postgres (the standing rule), covering:
- Access matrix. An UNAUTHENTICATED (no-session) request can READ a
public project (canBrowse true with no account — the page is fully public), as can a cross-org account; canEdit is FALSE — assert every normal write (create / move / assign / status / field-edit) is rejected. The three grants succeed ONLY for a signed-in account: canSubmitToTriage, canUpvotePublicRequest, canCommentPublicRequest each allow their write for an authed account on a public project, and are REJECTED for an unauthenticated request (sign-in-to-act). A NON-public project an unauthenticated/cross-org request hits is 404-not-403 (the cross-org read exception is public-only).
- The public projection. Assert the public read shape EXCLUDES assignees, estimates, and internal comments from the payload (not just the DOM) while including the public-safe fields + public-request comments.
- Duplicate detection. A draft title matching an existing public request returns the candidate(s) before creation; choosing it creates NO new item; "submit as new" creates one. The match is deterministic (no AI).
- Voting. One vote per account per item (the unique
(workItemId, userId) holds; a second upvote is a no-op / toggle, no double count); the vote count is the 6.11.3 queue sort key (assert the queue orders by demand); a concurrent vote serializes via the row lock.
Acceptance criteria
- The access matrix is asserted: anonymous + cross-org READ allowed on public, every normal write blocked, the three grants allowed only for a signed-in account (rejected when unauthenticated), non-public 404-not-403.
- The public projection’s hidden-field exclusion is asserted at the payload level; duplicate detection (match → upvote-existing vs submit-new) and one-vote-per-account + the queue sort are each asserted.
- New service/repository code respects the per-file coverage gate (CLAUDE.md § coverage); the empty-input / no-membership / already-voted guards each have a direct test; tests use the real Postgres helper.
Context refs
- 6.12.3 (access extension + grants + vote model), 6.12.4 (the projection), 6.12.5 (dedupe), 6.12.6 (voting + comments).
motir-core/CLAUDE.md § tests-use-real-Postgres + the per-file coverage gate.
motir-core/tests/helpers/db.ts — the per-test truncation harness.