MOTIR-410Done
6.12.2 Decision — the `public` access-level semantics: cross-org read, write-grants, visible-vs-hidden, the openness ladder
Estimate: 45m
Type: decision (the keystone ADR the schema + access cards [6.12.3+] build against; no app behavior ships, but the shapes it fixes are load-bearing). Write motir-core/docs/decisions/public-projects.md, EXTENDING (not forking) 6.4’s access-level ADR. It MUST fix:
publicextendsProjectAccessLevel(open / limited / private / public). Addpublicto the existing 6.4 enum; the openness ladder is public > open > limited > private. State that this is a one-value extension of the 6.4 model + the SAMEprojectAccessServicepolicy — NOT a parallel access system.public= ANYONE reads CROSS-ORG, no sign-in (the single exception). Decide precisely: 6.4’scanBrowsereturns true for ANYONE — INCLUDING an unauthenticated/anonymous request — when the project ispublic, BYPASSING the 6.10 org/workspace membership gate FOR READ ON PUBLIC PROJECTS ONLY. The public read is server-rendered + crawlable (SEO/GEO). Every other level stays org/workspace-bounded; the 404-not-403 cross-tenant posture is preserved for non-public projects (a cross-org user hitting a non-public project is still not-found, never forbidden). Fix WHERE this exception lives so it is a single, auditable branch in the access policy (not scattered).- Writes limited to triage-submit + upvote + comment — explicit grants, NOT a
canEditrelaxation. A public viewer is not a member, so 6.4canEditis FALSE for every normal write (create/move/assign/status/field-edit). The three permitted writes are NEW narrow capabilities checked explicitly:canSubmitToTriage,canUpvotePublicRequest,canCommentPublicRequest— each true for any authenticated account on a public project, each independent ofcanEdit. State that no other write path may ever key off "is on a public project". - Visible vs HIDDEN — the public projection. Decide the EXACT set of internal-only fields stripped from the public read: assignees, estimates, and internal comments are HIDDEN (decide explicitly which comments are "internal" vs public-request comments — the public-request comment thread from 6.12.6 IS public; the work item’s internal discussion is not). Fix that the stripping is a PUBLIC PROJECTION at the read layer (a dedicated read shape / DTO that never includes the hidden fields), NOT a UI that fetches everything and hides it (which would leak over the wire). Enumerate what IS visible: work item key/title/kind/status/description, board columns, the public roadmap, vote counts, public-request comments.
- READ anonymous; WRITE requires sign-in (revised 2026-06-14). Fix that READING a public project needs NO account (anyone, logged out, crawlers) — the page is server-rendered + crawlable (SEO/GEO). The three WRITES (submit / upvote / comment) require a signed-in account, so each is attributed + rate-limited by it; a logged-out write surface shows a sign-in-to-act prompt. Anonymous writes are out of scope (future — abuse + anonymous-identity model). State the share-link opens the public project with NO sign-in.
- Submission + dedupe + vote model semantics. A public submission reuses 6.11’s intake (born a triage
work_item), attributed to the cross-org account. Fix the duplicate-detection contract (a deterministic title/text match over existing PUBLIC requests, surfaced BEFORE create so the user upvotes the existing one — Canny’s behaviour) and the vote model (one vote per account per item, server-enforced; the count is a sort key the 6.11 triage queue reads). Decide the vote storage (aPublicRequestVotejoin, unique on(workItemId, userId)).
Acceptance criteria
- The ADR fixes all six sections, naming the FOUR-level ladder (public > open > limited > private) as a one-value extension of 6.4’s enum + the SAME
projectAccessService. - It states the cross-org READ exception is public-only and lives in a single auditable branch, and that 404-not-403 holds for non-public projects.
- It enumerates the three explicit write grants (submit / upvote / comment) as independent of
canEdit, and the EXACT hidden-field set (assignees, estimates, internal comments) stripped by a public PROJECTION at the read layer. - It fixes the anonymous-READ / sign-in-to-WRITE rule (+ SEO/GEO crawlable public read), the duplicate-detection-before-create contract, and the one-vote-per-account model with its storage.
- It cites the verified mirror (OpenProject/Plane/GitHub public visibility + Canny/Productboard/Featurebase portal set) for the public-project + submit/upvote/comment/status-roadmap shape.
Context refs
scripts/plan-seed/data/story-6.4.ts— the shippedProjectAccessLevel(open/limited/private) + theprojectAccessServicecanBrowse/canEditpolicy this extends.scripts/plan-seed/data/story-6.11.ts— the triage intake (6.11.4) + queue (6.11.3) the public submit reuses + the vote-count sort feeds.scripts/plan-seed/data/story-6.10.ts— the org gate the public cross-org READ exception bypasses (for public projects only).motir-core/lib/services/workItemsService.ts— the write authority submit/upvote/comment route through.- Canny (https://canny.io/use-cases/feature-request-management) — duplicate detection + upvote-the-existing + status roadmap; OpenProject (https://www.openproject.org/roadmap/) + Plane (https://plane.so/open-source) — public project / public roadmap visibility.