Skip to content

feat(git): private repositories with channel-scoped access #2469

Description

@LordMelkor

Why: the user story

Buzz teams live in channels. A channel holds the whole life of a project — the
strategy discussion, the design argument, the agent that drafts the
implementation, the review that reshapes it. Channels can be private, and teams
rely on that: what is said in the channel stays with the channel's members.

The code does not. Today, the moment anyone on the team — human or agent —
pushes that work to a Buzz-hosted repository, it becomes readable by every
authenticated user on the relay. The privacy model holds for the conversation
and silently breaks at its most valuable artifact.

As a team collaborating in a private channel, I want the repositories our
work produces to be exactly as private as the channel itself, so that inviting
someone into the team is the only access decision I ever have to make.

Concrete situations this serves:

  • A small team on a shared relay builds something unannounced. They cannot
    self-host a relay just to keep one repository quiet, and they should not have
    to: the channel already defines who is on the team.
  • An agent-heavy workflow where managed agents turn private channel
    discussions into commits. Without private repositories, an agent pushing code
    is an unintentional publishing act — the conversation was private, the diff is
    not.
  • A relay operator hosting several groups (a community server, a
    consultancy, a company with multiple teams). Each group's channel isolates
    their discussion; their repositories need the same tenant boundary or the
    groups can read each other's source.
  • Offboarding. When someone leaves the team, removing them from the channel
    should be the whole procedure. Membership-based access means their repository
    access ends on their next request — no separate ACL to remember, no stale
    deploy keys.
  • Not confirming a project exists. An outsider probing clone URLs learns
    nothing: a repository they cannot access answers exactly like a repository
    that was never created.

The design consequence: channel membership is the access list. Buzz already
has a first-class notion of "the team" — the channel — with invitation,
removal, and roles. Private repositories reuse it rather than introducing a
second permissions registry that can drift out of sync with the team it is
supposed to describe.

Problem

Buzz-hosted Git repositories are currently readable by any authenticated relay
member. A repository can carry a buzz-channel binding for push authorization,
but that binding does not restrict discovery, browsing, clone, or fetch.

This prevents teams from safely hosting repositories that should only be
available to a specific channel. Protecting pushes alone is not sufficient: a
non-member who knows the clone URL can still read the repository.

Requested behavior

Add explicit private visibility to NIP-34 repository announcements:

  • A repository owner can mark a repository private and bind it to one Buzz
    channel.
  • The repository owner, the verified owner of a managed-agent repository, and
    current members of the bound channel can discover and use the private
    repository.
  • Other authenticated users cannot discover it through repository announcements
    or state events, search, listing, counts, live subscriptions, or the HTTP
    bridge.
  • Apply the same access check to every Smart HTTP Git surface: info/refs,
    git-upload-pack, and git-receive-pack.
  • Return the same generic 404 repository not found response for a denied
    private repository and a repository that does not exist.
  • Resolve repository visibility and channel membership at request time so
    membership removal takes effect on the next request.
  • Keep channel roles and branch protection responsible for write authorization
    after the repository-level access gate succeeds.
  • Expose private visibility and channel selection in Desktop and the buzz repos
    CLI.
  • Preserve existing behavior for repositories that do not explicitly opt into
    private visibility, including repositories that already use buzz-channel
    only as a push binding.

Acceptance criteria

  • A repository can opt into private visibility with one channel UUID.
  • Creating or updating a private repository rejects missing, malformed,
    duplicate, or unsupported visibility/channel metadata.
  • A private repository announcement is rejected if its owner is not a
    current member of the selected channel.
  • The repository owner, a verified managed-agent owner, and current channel
    members can discover, browse, clone, fetch, and—subject to existing write
    policy—push to the private repository.
  • A non-member cannot discover the private kind 30617 announcement or
    associated kind 30618 repository state through WebSocket queries, HTTP
    queries, search, counts, pagination, or live subscriptions.
  • A non-member receives a generic 404 repository not found from
    info/refs, git-upload-pack, and git-receive-pack, even when using
    the exact repository URL.
  • Removing a channel member revokes discovery and Git access on that
    member's next request.
  • Repository counts remain exact after access filtering; overly broad
    discovery counts are rejected rather than truncated.
  • Public and legacy repositories retain their existing read behavior unless
    explicitly marked private.
  • Changing a repository back to public preserves any existing buzz-channel
    push binding.
  • Desktop supports selecting private visibility and a channel during
    creation, and owners can change visibility from project details.
  • The CLI supports private creation and visibility edits with clear
    validation and relay rejection errors.
  • Automated tests cover visibility parsing, live membership revocation,
    discovery and counts, live fan-out, pagination, legacy compatibility, and
    all Smart HTTP endpoints.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions