Mention-grants: Collaborator-based cross-space access for @-mentions#10895
Open
MichaelUray wants to merge 28 commits into
Open
Mention-grants: Collaborator-based cross-space access for @-mentions#10895MichaelUray wants to merge 28 commits into
MichaelUray wants to merge 28 commits into
Conversation
Adds a model-level boolean that classes can set alongside provideSecurity to indicate that @-mentions in chat/activity messages on docs of this class should auto-create Collaborator records, granting the mentioned user explicit, disclosed access. The flag has no effect unless provideSecurity is also true. provideSecurity itself is unchanged (read-visibility OR-branch through the SpaceSecurity middleware). Without mentionsGrantAccess, todays "mention is a no-op silently" behavior is preserved for QMS and Love classes. Step A1 of the plan in /opt/infrastructure/docs/superpowers/plans/ 2026-05-25-huly-mention-grants-collaborator-access.md. The accompanying model wiring (A2), Tracker opt-in (A3), middleware veto (A4), shared helper (B-1), chunter trigger update (B-2), and client surfaces (B.1, B.2, C) follow in subsequent commits. Refs hcengineering#10783, hcengineering#9741. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit fcc426fa2214079e8fbff7f2074b7778b733bc7b) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…odel class Mirrors the new field in the runtime model schema. Follows the existing bare-annotation pattern used for the other ClassCollaborators fields (no @prop decorators on this model class today). Field is model-internal — never appears in user-facing labels, so no IntlString or locale entries needed. Step A2. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 91359230610a9d9ce3d2da333e476753e3102fc2) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…+ mentions-grant-access Sets provideSecurity:true and mentionsGrantAccess:true on the Issue ClassCollaborators declaration. This is the single class opting into the new behavior in this PR — QMS, Love, Cards and other classes are unaffected. Effect together with subsequent commits: - A user @-mentioned on an Issue they are not a project-member of is auto-added as Collaborator on the Issue (B-2 chunter trigger). - The Collaborator record grants them read visibility (provideSecurity in SpaceSecurity middleware). - They can post comments (chunter.class.ChatMessage createAccessLevel is already Guest). - They cannot edit Issue fields (A4 GuestPermissions middleware veto). Step A3. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit bbf25b667461793c2964181983d10bdd2a29fb2e) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…lper Walks a Docs attachedTo chain (depth-capped at 8) to find the nearest ancestor whose ClassCollaborators has both provideSecurity:true AND mentionsGrantAccess:true. Returns that ancestor as the grant target, or null if none. Isomorphic via the findAll dependency injection — same code used by both the server-side chunter mention-trigger (B-2) and the client-side mention warning popup (C), so the disclosure UX matches the actual server-side grant. For ThreadMessage mentions: walks from ThreadMessage -> parent ChatMessage -> parent Issue, and writes Collaborator on the Issue, not on the thread or chat message. Step B-1. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit f7b9a9909b36ab21e3fc92994d02576158e66779) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ly guests on opt-in classes Adds isForbiddenCollabOnlyGuestFieldUpdate to GuestPermissionsMiddleware as a class-agnostic, pre-commit veto. Triggered only when the targeted class has both provideSecurity:true AND mentionsGrantAccess:true on its ClassCollaborators model entry. Tracker Issue is the first opt-in (set in models/tracker/src/index.ts), but any future class with the same flags inherits identical semantics with zero additional code. Semantics: - User+ accounts pass through (unchanged). - Space-member guests pass through (their existing rules apply). - Guest-tier accounts that are NOT space-members but reach the doc via Collaborator status (provideSecurity OR-branch in SpaceSecurity middleware) get a Forbidden when they try to TxUpdateDoc the doc. Effect for the user-visible scenario in hcengineering#10783: - Florian @-mentioned on GAME-4 in a project he is not a member of becomes Collaborator on GAME-4 (auto, via B-2 chunter trigger). - He gets read visibility (provideSecurity). - He can post comments (ChatMessage createAccessLevel: Guest). - He CANNOT modify GAME-4 (status, title, dates, ...) — this veto. - Florian editing his own OSKOS-12 in Ostrowo where he IS a member is unaffected: space.members.includes(florian) → veto returns false. Step A4. Refs hcengineering#10783, hcengineering#9741. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 8e825a54db51f6b5eaa392d8d961a37ad4da2f7b) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…CommentOnIssue Previously canEditIssue conflated "may edit fields" with "may comment". A guest who was Collaborator on an issue ended up with all field editors enabled (too permissive) — or, when blocked, lost the comment composer too (too restrictive). The split: - canEditIssueFields: Issue field editors (title, status, dates, description, ...). Returns false for any guest-tier account. - canCommentOnIssue: Comment composer. Returns true for User+, false for ReadOnlyGuest, and for Guest/DocGuest true when the user is the Issues creator OR listed as Collaborator on the issue. Existing canEditIssue is kept as a backwards-compat alias for canEditIssueFields until callers are migrated. EditIssue.svelte already migrates in the next commit (B.2). Pairs with the server-side veto in A4 (GuestPermissions middleware) that rejects TxUpdateDoc<Issue> for collab-only guests at the tx layer — so a guest cannot route around this UI gate via raw API. Step B.1. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit b49c7c59e815433a0a38f7d25248d2bc078110a5) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…editors EditIssue.svelte used a single effectiveReadonly flag to gate both the issue-field editors (title, status, dates, description, dependencies, ...) AND the Panels comment composer below them. With the v6 split a Collaborator-Guest must be able to comment without unlocking the editors, so the two need separate variables. Adds canComment alongside effectiveReadonly: - effectiveReadonly stays driven by canEditIssueFields() — Guest gets read-only field editors. - canComment is driven by canCommentOnIssue() — Guest who is the Issues creator or listed as Collaborator gets the comment composer. Panels withoutInput now reads !canComment; all field-editor readonly props keep using effectiveReadonly. Step B.2. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 7449ad0d435c2eb43f12c8b1b41f31702eb6ac48) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
ChatMessageInput.svelte imports extractReferences from text-core for the mention warning popup. The package was already an indirect dep via text-editor-resources but webpack module resolution requires it as a direct entry in package.json. Added; pnpm-lock regenerated via rush update. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 7c58a562eeaaf54f94361e8a52cb9cb112fd2766) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…nt-target dedup Replaces the prior provideSecurity!==true guard with three explicit branches driven by resolveMentionGrantTarget(): 1. grantTarget non-null (opt-in class found via attachedTo chain): write Collaborator records on the grant-target Doc with dedup against the GRANT-TARGETs collaborator list, not the original message Docs list. Fixes the thread-reply case where the mention would otherwise land on the parent ChatMessage instead of the Issue. 2. grantTarget null + targetDoc not provideSecurity: keep todays notification-routing behavior on targetDoc (Channels, DMs). 3. grantTarget null + targetDoc provideSecurity (without opt-in): no-op. Preserves QMS / Love behavior bit-for-bit. Step B-2. Refs hcengineering#10783, hcengineering#9741. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit ad78e4edb0b953883a904e50ea8adaab23aee1c1) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
… access Disclosure UX for the mention-grants-access flow. Before submitting a message containing @-mentions, check whether the resolved grant-target Doc (via the shared resolveMentionGrantTarget helper) has those mentioned users in its space.members list. If any mention would grant new access, show a MessageBox warning with the actor and the names of the new grantees, and require explicit confirmation. Important framing: - This is disclosure UX for the standard client, NOT a security gate. Access is enforced server-side by SpaceSecurityMiddleware + the chunter trigger that creates Collaborator records. A scripted API client can still bypass the dialog. - For thread replies: the grant-target resolves to the root Issue, so the warning correctly names the project the user would gain access to (not the thread). - For Channels and other non-opted-in classes: grantTarget is null, no warning shown — preserves todays behavior bit-for-bit. Step C. Refs hcengineering#10783, hcengineering#9741. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit ab212dbf61a828334ceb88ca470466b8c40d3fdd) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…cross spaces
SpaceSecurityMiddleware.findAll() pre-filtered every query by the
caller's allowed-spaces set, which always strips docs in foreign spaces
before the database adapter ever sees the query. That defeated the
Postgres adapter's `collabRes` OR-branch (see postgres/src/storage.ts,
getSecurityClause): a Guest who is a Collaborator on a single Issue in
a project they are not a member of would never receive that Issue, even
though the adapter has the SQL to surface it.
This change skips the middleware-level space filter when:
- the target class has ClassCollaborators.provideSecurity === true
or provideAttachedSecurity === true, AND
- the caller's role is Guest or ReadOnlyGuest.
For those calls the Postgres adapter still applies its
space-membership clause and additionally OR-joins Collaborator records,
giving the user visibility on the individual docs they were added to
(directly via provideSecurity, or via their parent via
provideAttachedSecurity for ActivityMessage / DocUpdateMessage).
All other roles (User, Maintainer, Owner, Admin, DocGuest, System)
take the existing code path unchanged.
Note for non-Postgres adapters: the Mongo adapter does not currently
implement the collab OR-branch, so this bypass only takes effect on
Postgres deployments (the supported production target). On Mongo the
visibility behavior is unchanged from before because there was no
collab OR-clause to fall through to.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit 5db91841cae1fc9bdad898229e8b213f72a72c6b)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…or records
The tracker "Subscribed" tab in the client queries
`findAll(Collaborator, { collaborator: self, attachedToClass: Issue })`
to build the user's subscription list. Until now that query was
silently filtered to spaces the user is a member of, so a Guest who
became a Collaborator on an Issue in a project they are not a member
of (e.g. via the new mentions-grant-access flow) saw an empty
Subscribed list.
This change introduces a self-Collaborator visibility rule consistent
across the two enforcement layers:
- foundations/server/packages/postgres/src/storage.ts
`addSecurity` now appends `OR <domain>.collaborator = '<acc>'`
whenever the queried domain is DOMAIN_COLLABORATOR. The user can
always see their own Collaborator rows.
- foundations/server/packages/middleware/src/spaceSecurity.ts
`findAll` skips its space pre-filter when the requested class is
core.class.Collaborator (`selfCollabBypass`), so the Postgres
adapter actually receives the query and can apply its self-row
OR-branch. Other rows in the same workspace remain hidden by the
space-membership clause that still runs first.
Scope: applies to all non-Admin/non-System callers (Users included).
Users were not visibly affected before because they are typically
members of every space whose docs they collaborate on, but the rule
is the same: you may read your own subscription rows.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit 51fa7ba162a7d1df7ed382c50d915e4fde4fab68)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…r Guests
A Guest who is a Collaborator on a doc inside a Space they are not a
member of (e.g. via the new mentions-grant-access flow) could open
the doc directly via URL and see it listed in tracker "Subscribed",
but the containing project never appeared in the "Your Projects"
nav tree because that tree only listed member-spaces.
This change extends visibility on three layers:
- foundations/server/packages/postgres/src/storage.ts
`addSecurity` adds an `OR EXISTS (collaborator c WHERE c.space =
space._id AND c.collaborator = '<acc>')` branch for Guest/
ReadOnlyGuest reads against DOMAIN_SPACE. A Space hosting any
Collaborator record naming the caller becomes readable.
- foundations/server/packages/middleware/src/spaceSecurity.ts
`findAll` skips its space pre-filter when the target class is a
Space and the caller is Guest/ReadOnlyGuest (`spaceCollabBypass`),
so the new Postgres OR-branch actually fires.
- plugins/workbench-resources/src/components/Navigator.svelte
A second query against Collaborator (scoped to self by A6's self-
collab visibility rule) collects the unique `space` IDs of the
caller's Collaborator records, fetches those Spaces narrowed to
the navigator's class set, and merges them into the displayed
spaces alongside member-spaces. Existing member-spaces semantics
for User+/Admin accounts are unchanged: the second query is
skipped for admins, and `members: <self>` stays on the primary
query for everyone, so public-but-not-member projects are not
surfaced as a side effect.
Clicking a collab-only project still opens the existing Issues view,
which now shows only the docs the user can actually see (member-or-
collab-on-doc) thanks to the earlier A5/A6 work. Components, Milestones
and Templates sub-nodes will show empty for collab-only projects.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit 64105d1f14bed4ef18a330861937273685fcc21c)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The workbench Navigator already fetched collab-only Spaces and merged
them into the spaces array, but each SpacesNav then ran its
`visibleIf` resource filter against the entries — and tracker's
`IsProjectJoined` only returned true for members. The result was that
collab-only projects were correctly fetched, hashed and passed to the
component, then immediately filtered back out before render.
Changes:
- plugins/workbench-resources/src/components/Navigator.svelte
Refactor activeClasses into a top-level reactive declaration so
Svelte tracks it as a dependency in the downstream collab-space
query. Drop the diagnostic console.log lines that helped track
this down.
- plugins/tracker-resources/src/index.ts
Extend IsProjectJoined: return true also when the caller has any
Collaborator record attached to a doc inside this project. Reuses
A6 self-collaborator visibility so the lookup works for Guests
that are not space members.
Verified end-to-end against the dk3 test workspace: Florian (Guest,
collab on GAME-4 only) now sees the Game Design project in his Your
Projects tree, with the Issues special filtered down to only the docs
he is actually a Collaborator on. Components / Milestones / Templates
remain empty for the same caller.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit 97b58bd5063bb690211e8bc40fbf024cb7e00aae)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
A9 follow-up to the mention-grants nav-tree work. When a Guest sees a project in 'Your Projects' because they are a Collaborator on a doc inside it (A7) — or any non-member who lands a project in the tree via the new IsProjectJoined fallback (A8) — only the Issues sub-node has anything to render. Components, Milestones, and Templates have no provideSecurity opt-in, so the postgres collab-OR-branch does not fire on them and the queries come back empty. The user sees three silent dead-end entries. Fix in ProjectSpacePresenter: derive isCollabOnlyProject from space.members membership and filter the specials down to id == 'issues' when the caller is not a member. Members keep all four sub-nodes; the visibleIf chain for collab-only projects keeps Issues and nothing else. This is a UI-only narrowing. Backend visibility is unchanged: A5/A6/A7 still control what the user can actually see when they navigate elsewhere, the postgres adapter still does its filter, and the middleware bypass remains scoped to provideSecurity classes. If a future class opts into provideSecurity / provideAttachedSecurity for a project sub-collection (Components etc), the filter here can be relaxed without touching the underlying security model. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit c76073c0bf01601bd76150626005cae33a51bf88) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Reuse the existing isCollabOnlyProject reactive to dim the icon (0.6 opacity) and expose a tooltip on projects the user only sees via a mention-grant rather than membership. Tooltip uses the use:tooltip action so the IntlString resolves internally (no Promise in an attribute). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 2227bed8f567c4824247d2a56639b3e36b382485) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Regression guard: a user mentioned in an issue comment stays a Collaborator (the record that backs read access) after the issue is moved to Done. Built on the sanity page-object helpers, mirroring the existing mentions.spec assertion style. Guest-perspective UI walk is a documented follow-up (needs a second auth storage state). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 27d2c58103cc5e530fba3af851a6426081400a42) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Add a string grantsAccess ('true'|'false'|undefined) to ReferenceMarkupNode
and surface it on extractReferences() with any-wins dedup (a person is
denied only if every reference to them is explicitly 'false'). undefined
preserves pre-V3 grant-by-default. Tiptap ReferenceNode keeps the
data-grants-access attribute across editor round-trips.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit e21951022c909236113ade6aa75498b204fa595d)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Move the grant decision from the context-free mention popup into the send-time disclosure, which already knows the grant target, space members and new grantees. The dialog shows a checkbox per new grantee; unchecking rewrites every reference to that person in the message markup to grantsAccess='false' (all of them, so any-wins cannot re-grant) via the pure applyMentionGrantChoices helper. Existing members' mentions are left untouched. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 49c5117b5de572ff9744e6110fc1d7d161aa529e) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The mention-grants trigger now skips Collaborator creation for any reference whose grantsAccess is explicitly 'false' (set by the V3b send-time disclosure). undefined still grants (pre-V3 default). This is the authoritative server-side enforcement; the client disclosure filter is UX only. Covered by a ChunterTrigger unit test that asserts a denied mention produces no Collaborator tx. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 1c8cd3efd771e89df017e8d6b740117a9acf2b46) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Add an OnChatMessageUpdated branch that re-runs the mention-grant logic on an edited message's new text. It only ADDS Collaborator records (existing ones dedup to no-ops) and never removes: TxUpdateDoc carries no pre-update state to diff against, and Collaborator has no provenance to safely remove mention-sourced grants by. Revoke-on-removal is deferred to a future provenance model (shared with the rendered-mention revoke stream). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 650ed5e78732202897b750ddb00de9e18a52fb4d) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
OnChatMessageUpdated rebuilt the message via { ...current, message } which
kept the original author's modifiedBy, so applyMentionGrants' System guard
checked the wrong actor. Use TxProcessor.updateDoc2Doc so modifiedBy
reflects the edit tx. Add ChunterTrigger unit tests for the update path:
new-mention grant, denied-mention (V3c) on edit, existing-collaborator
dedup, System-authored edit grants nothing, and non-message update no-op.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
(cherry picked from commit 7cb590ff1c6b0bd4ff6dac17e5e3f5d7c38a86b9)
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
V3b made the send flow async (the per-grantee disclosure popup), but onMessage removed the draft and cleared the input BEFORE the popup resolved and did not await handleCreate. Cancelling the grant dialog therefore lost the unsent comment. handleCreate/handleEdit now return a boolean; onMessage awaits both and only drops the draft + clears the input + dispatches submit on a successful send/edit. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 34bc13a333318c2bc04457ae1cdcc87f5b97ca4a) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…covered-class test isForbiddenCollabOnlyGuestFieldUpdate (added by 8e825a54db) walks the class hierarchy via getClassCollaborators → getAncestors. The "uncovered class" test scaffold patched classHierarchyMixin and isDerived but not getAncestors, so the new code path threw "ancestors not found: test:class:UncoveredClass" before reaching the production-style early-return. Returning [] from the stub matches the intent: an uncovered class has no ancestors to inherit ClassCollaborators from. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…<Class<Space>>[] 64105d1f14 introduced the activeClasses derived reactive but cast it to Ref<typeof core.class.Space>[]. Since `typeof core.class.Space` is already Ref<Class<Space>>, that double-wrapped the type to Ref<Ref<Class<Space>>>[], which svelte-check correctly flagged as incompatible with the collab/member query callsites that expect Ref<Class<Space>>[]. Drop the extra Ref<> wrapper and import Class so the cast is to the actually intended Ref<Class<Space>>[]. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Address findings from the independent self-code-review: - B-NEW-2 (BLOCKER): Tiptap grantsAccess attribute parsed to null instead of undefined, violating the public type 'true'|'false'|undefined. parseHTML now normalises null -> undefined, default -> undefined. Plus the node-level renderHTML now explicitly emits data-grants-access (belt-and-suspenders). - B-NEW-3 (BLOCKER): V1 emoji-iconned collab-only projects were not dimmed. The IconWithEmoji branch now also carries opacity: 0.6. - B-NEW-1 (BLOCKER): sanity addMentions helper hung on the new V3b disclosure popup. It now confirms 'Send with selected grants' when the dialog appears (1s timeout for the legacy non-disclosure path). - I-NEW-6 (IMPORTANT): V1 reactive miss — updateSpecials did not re-fire on isCollabOnlyProject changes, leaving stale specials when the user got added to/removed from the project mid-session. Pass collabOnly as a parameter so Svelte tracks the dep. - I-NEW-7 (IMPORTANT): trigger test used require() which fails strict tsc (the package types only include @types/jest). Replaced with ESM import. - I-NEW-8 (IMPORTANT): applyMentionGrantChoices guarded n.attrs !== undefined, which lets null through and throws on n.attrs.id. Use != null instead. All 18 mention unit tests stay green (V3a 7/7, V3b helper 5/5, V3c+V3d trigger 6/6). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 33fb48075e73918e5b97c1acc14c29d79fe1abfc) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Sample/auto-templated tracker Projects store members=NULL in cockroach (no explicit member array). The V1 isCollabOnlyProject expression did 'space.members.includes(uuid)' without a null guard, so it threw a TypeError on those projects. Svelte's reactive block swallowed the throw, leaving isCollabOnlyProject undefined -> the badge + sub-node hide silently broke. Found in MentionTestWS 'Welcome to Huly!' and test-workspace 'Game Design (Example)'. Fix: '(space.members ?? [])'. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> (cherry picked from commit 8299469b72a53903c45d2fb7d773e097b1d77c64) Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The hard-coded 'sp.id !== "issues"' check in updateSpecials assumes tracker.class.Issue is the only class with mentions-grant-access opt-in. That holds today (only Issue carries ClassCollaborators.mentionsGrantAccess = true). If a future change extends collab-grants to Components, Milestones, or Templates, this check will silently hide those views for collab-only Guests. Add a comment so the next person to flip an opt-in sees the implicit dependency. No behavioral change — comment only. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
|
Connected to Huly®: UBERF-16493 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mention-grants: Collaborator-based cross-space access for @-mentions
When a user @-mentions someone on a doc whose class is opted into mentions-grant-access, that mentioned account should gain read access to the doc — even if they are not a member of the doc's containing space. This is the cross-space access path that today's Collaborator infrastructure already supports for explicit
addCollaboratorcalls; this PR wires it up to the @-mention authoring flow with an opt-in disclosure dialog.Companion docs: hcengineering/huly-docs#72 (draft; text complete, screenshots will be added in a follow-up commit on the same branch).
What this builds
V1 — Cross-space Space-visibility for Guests. Guests who hold a Collaborator edge to a doc inside a space they are not a member of need that space to surface in their navigator so the doc is reachable without deep-link gymnastics. Three layers cooperate:
foundations/server/packages/middleware/src/spaceSecurity.ts— skip the middleware-level space pre-filter forGuest/ReadOnlyGuestreads of Space-derived classes (spaceCollabBypass). The Postgres adapter then does the filtering at SQL level.foundations/server/packages/postgres/src/storage.ts—addSecurityappends anOR EXISTS (collaborator WHERE space = domain._id AND collaborator = caller)branch for Guest reads onDOMAIN_SPACE. A Space hosting any Collaborator record naming the caller becomes readable. Self-Collaborator visibility for non-Admin non-System callers is added in the same change.plugins/workbench-resources/src/components/Navigator.svelte— client-side: union ofmember-spaces+collab-spaces. Thecollab-spacesset comes fromfindAll(Collaborator, { collaborator: self })withprojection: { space: 1 }, then a batchedfindAll(Space, { _id: { $in: ... } }). Tracker's per-project tree marks collab-only projects with a dim icon + tooltip ("Shared with you") and hides non-Issues sub-views (Components / Milestones / Templates) because they would render empty for a collab-only Guest.V3 — Send-time disclosure for @-mentions. When the author types
@person, the picker passes the user-intent into a Markup attribute on the reference node (grantsAccess, values:'true' | 'false' | undefined). On send, if the message contains any reference withgrantsAccess !== 'false'and the mentioned employee is not already a member of the doc's space, a disclosure dialog asks the author to confirm per grantee. (The server trigger additionally dedups against existing Collaborator records, so the dialog may surface for a user who already has the grant; the trigger then emits no new Tx.) Cancelling the dialog preserves the unsent comment.V3c (server filter) —
mentionsGrantAccesstrigger ignores any reference whosegrantsAccessis explicitly'false'. V3d (edit path) — re-grants on a message edit are strictly add-only: a removed mention does not retract anyone's read access (a previously-granted user remains a Collaborator until an explicitremoveDocon the Collaborator).Architecture sketch
What is opt-in
ClassCollaborators.mentionsGrantAccess: true(defaultfalse).tracker.class.Issueopts in.Tests
foundations/core/packages/corefoundations/core/packages/text-corereference.test.ts)plugins/chunter-resourcesmentionGrants.test.ts)server-plugins/chunter-resourcesmentionGrantsTrigger.test.ts)foundations/server/packages/middlewarerush install/rush build/rush validate: all exit 0.svelte-checkacrosstracker-resources/chunter-resources/workbench-resources: 0 errors.Live deployment evidence
Deployed and verified on a self-hosted Huly v0.7.423 instance with the CockroachDB backend. Specifically exercised:
EXISTSquery againstcollaboratortable returns the expected projects).Sign-off
Single author, signed-off-by on every commit, DCO-compliant. 28 commits, intentionally not squashed for traceability. Squash-merge fine if preferred.