Conversation
Four pages describe a project dimension on the human path that the data model does not have. There is no `project_members` table, `roles` is `(orgId, name, permissions[])` with no project column, and permission strings carry no project either — so a role granting `workspaces:execute` grants it in every project in the organization, and "maintainer on one repository" cannot be written down at all. The claims corrected: - `self-host/authentication.md` — "Any active project maintainer can read and continue the same story. Every project operation checks organization and project scope." - `self-host/authentication.md` — the preview proxy "revalidates the user, project membership, ...". `WorkspacePreviewService.assertMembership` queries `org_members`; the check is organization membership. - `contributors/architecture.md` — the request path "checks project scope and permissions". - `guides/operate-story.md` — the UI "can continue it under the current user's project membership". `reference/security.md` was already the accurate page. Its sentence is now the canonical one and says plainly that a project boundary does not contain a person, and that a project-scoped API key is the only mechanism that contains a principal to one project. This matters because the pages sit next to a genuinely careful piece of work — a scoped key asking for another project gets 404, not 403, so it cannot be used as an enumeration oracle. A reader who has just read that will reasonably assume the same containment applies to people. It does not, and nothing in the model made the asymmetry visible. Provisioning against the stronger reading is a security expectation set by documentation. Documentation only: no behaviour changes, and no position is taken on whether the model should gain a project dimension. That question stays open in theam#325. Refs theam#325.
# Conflicts: # apps/docs/docs/guides/operate-story.md
…ber's story `stories-and-workspaces.md` landed in theam#368 with the same claim this branch corrects elsewhere: "A project member can continue a story created by another member." There is no project membership. `orgMembers` is `(orgId, userId, roleId)` and permission strings carry no project, so what allows continuing someone else's story is an organization role that grants it — in every project in the organization, not in one. Same correction as the other four pages, on a page that did not exist when this branch was opened.
adrian-lorenzo
left a comment
There was a problem hiding this comment.
Thanks for clarifying the organization-wide authorization model and updating the newer story docs.
One detail to correct: reading a story or conversation requires projects:read; workspaces:execute permits continuing it and does not imply read access. Please name both permissions in the authentication guide.
`workspaces:execute` was described as if it also granted read. It does not. Every `app.get` on a story or its conversation in `routes/v1/story-workspaces.ts` is guarded by `projects:read`; `workspaces:execute` guards continuing a story, opening its preview and suspending it. A role needs both to follow a story and act on it. Naming only one of them is the same failure this change set out to fix: prose that implies a containment the model does not have.
|
Addressed at 9e5ccd8. You are right, and naming only one of them was the same failure this PR set out to fix: prose implying a containment the model does not have. The guide now says |
What changes
Four documentation pages describe a project dimension on the human authorization
path that the data model does not have. They now say what the code does.
self-host/authentication.md— "Any active project maintainer can read andcontinue the same story. Every project operation checks organization and
project scope."
self-host/authentication.md— the preview proxy "revalidates the user,project membership, ...".
WorkspacePreviewService.assertMembershipqueries
org_members; the check is organization membership.contributors/architecture.md— the request path "checks project scope andpermissions".
guides/operate-story.md— the UI "can continue it under the current user'sproject membership".
reference/security.mdwas already the accurate page. Its sentence becomes thecanonical one and now says plainly that a project boundary does not contain a
person, and that a project-scoped API key is the only mechanism that contains a
principal to one project.
Documentation only. No behaviour changes, and no position is taken on whether the
model should gain a project dimension — that question stays open in #325.
Why
There is no
project_memberstable.rolesis(orgId, name, permissions[])with no project column, and permission strings carry no project either — so a
role granting
workspaces:executegrants it in every project in theorganization. "Maintainer on one repository" cannot be written down, so it cannot
be reviewed, audited, or refused.
This matters because the pages sit next to a genuinely careful piece of work: a
project-scoped API key asking for another project gets 404, not 403, so it
cannot be used as an enumeration oracle. A reader who has just read that will
reasonably assume the same containment applies to people. It does not, and
nothing in the model made the asymmetry visible. Provisioning against the
stronger reading is a security expectation set by documentation, which is why
this half is worth fixing regardless of how the model question in #325 is
answered.
Out of scope, deliberately. #325 also notes that the bundled
ownerandmaintainerroles are both["*"]— identical grants under different names, withnothing between "read everything" and "administer the organization". Narrowing
maintainerremoves access from existing instances on deploy, so it belongs inits own change with an expand → migrate → contract path, not in a documentation
correction.
reference/hardening.mdalready recommends project-scoped keys forautomation that needs only one project, so no change was needed there.
One claim listed in #325 is already gone: the README no longer says a "project
member" can open an authenticated local preview. Nothing to correct there.
Verification
Each claim was checked against the code before rewriting it, not against the
issue text:
pnpm verifypasses locallypnpm verifydoes not pass on this machine and not because of this change:test:devreports 116 tests, 92 pass, 24 fail here, and the identical 92/24on a clean
main— Windows noise unrelated to documentation. The meaningfulgates for a docs change are the link guard and the Docusaurus build, both green
above; the substance was verified by reading the schema, the role table and the
preview membership query rather than by any test.
Refs #325.