Vetrix Docs

Issue Comment Visibility

System documentation for the per-comment restriction model on issue comments — what IsRestricted=true means, who can read a restricted comment, and how the rule is enforced across the REST API and MCP issues toolset.

Data model

  • Table: issue_comments
  • Column: is_restricted BOOLEAN NOT NULL DEFAULT false (migration 000193).
  • Go field: issues.IssueComment.IsRestricted.

is_restricted=false is the default for every existing row at upgrade time and for every comment created without a body-level opt-in — the default visibility makes every comment visible to every repo-reader.

Visibility rule

A comment with IsRestricted=true is visible only to callers who satisfy at least one of:

  1. The caller authored the comment (caller.ID == comment.AuthorID).
  2. The caller is an instance admin (caller.IsAdmin == true).
  3. The caller owns the parent repo (caller.ID == repositories.owner_id).
  4. The caller is a repo maintainer — i.e. has a repo_collaborators row whose role is repo_admin or repo_write, OR role=custom with a permissions set containing repo:write or repo:admin.

Every other caller — including authenticated callers with only repo_read access — is denied. A non-restricted comment is always visible (subject to the repo-level read gate, which runs first).

The "maintainer" definition mirrors the canonical write-permission rule used by *git.Store.canPush (internal/git/authz.go). The two checks deliberately resolve "who is a maintainer on this repo" the same way so that a user who can push code on a repo can also see its restricted comments — no surprise gaps between the two surfaces.

Enforcement points

Accessor

issues.IsCommentRestricted (internal/issues/comment_visibility.go) is the single source of truth for the rule. Signature:

func IsCommentRestricted(
    ctx context.Context,
    comment IssueComment,
    caller CommentVisibilityCaller,
    repoLookup CommentRepoLookup,
    aclStore CommentVisibilityACL,
) (bool, error)

The accessor returns true when the caller must NOT see the comment, false when they may. Two narrow seams (CommentRepoLookup, CommentVisibilityACL) keep the accessor unit-testable without booting Postgres; the production wiring satisfies both seams via *issues.Store (LookupCommentRepo) and *acl.Store (GetCollaborator).

MCP list_comments

The MCP issues toolset wires IsCommentRestricted through the CommentVisibilityChecker seam. The production wiring in cmd/server/main.go installs mcpissues.NewProductionCommentVisibility(issueStore, aclStore), which delegates to IsCommentRestricted.

Restricted comments the caller cannot read are dropped from the page entirely — they are not surfaced with a redacted body, they are absent. This matches the contract documented on the CommentVisibilityChecker interface.

REST API

The REST GET /api/v1/repos/{owner}/{repo}/issues/{number}/comments handler does not filter on is_restricted. The current dataset has no restricted comments, so this does not change observable behaviour.

Setting the flag

issues.Store.CreateIssueComment writes is_restricted from the IssueComment.IsRestricted field on insert. issues.Store.SetIssueCommentRestricted(ctx, commentID, restricted) flips the flag on an existing comment; the UPDATE runs in the same tx as the searchindex outbox enqueue so the indexed view is durable iff the flag flip is.

issues.Store.UpdateIssueComment does NOT touch is_restricted — restriction state and comment body change independently and through different surfaces (the body via the REST PATCH …/comments/{cid} endpoint; the flag via issues.Store.SetIssueCommentRestricted).

Tests

  • internal/issues/comment_visibility_test.go covers:

    • Non-restricted comments are always visible (short-circuits before deps).
    • Author can read own restricted comment.
    • Instance admin can read any restricted comment.
    • Repo owner can read any restricted comment in their repo.
    • repo_write and repo_admin collaborators can read restricted comments.
    • Custom role with repo:write permission can read restricted comments.
    • repo_read collaborator is denied.
    • Non-member (no repo_collaborators row) is denied.
    • Anonymous (zero UUID) caller is denied.
    • Underlying lookup errors are surfaced (fail-closed).
    • Nil deps are rejected (wiring bug).
  • internal/mcp/issues/list_comments_test.go exercises the CommentVisibilityChecker seam with a fake fakeVisibility, so the production wiring is transparent to the unit tests.

Migration

db/migrations/000193_add_issue_comments_is_restricted.up.sql adds the column with DEFAULT false. In PostgreSQL 11+ this is a metadata-only change — no table rewrite, fast even on a populated table. The matching .down.sql drops the column. Migrations are single-statement so they apply cleanly inside golang-migrate's implicit transaction wrapper.