feat(git): private repositories with channel-scoped access
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## 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.
Contributor guide
Assessment
This issue has not been assessed yet.