vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Let the CLI do what the browser does for the same account
7e0b5950c190caf11089e1aaac10f18678074fed · Keenan Nemetz · 6d ago · parent 6e1fca51
Let the CLI do what the browser does for the same account A contributor could file an issue, comment, manage the item it wrote, and open a change from its own branch — but only in the browser, because a push to `refs/heads/vole` required `maintain`. The role was not the reason: the hook had no per-path authorization for that branch, so the role gate stood in for one. The per-path checker is now applied to every session, not only to run credentials. It asks the ACL the questions the browser's forms ask — CanDiscuss, CanOpenRequest, CanTriage, CanApprove — so a contributor's key can write what a contributor may write, and a maintainer's key is unchanged. A non-maintainer's new file must name that account as its author, which is what the browser does for it; a maintainer keeps writing an imported issue under its original author, because that is a trust decision rather than a capability. Creating the issues branch still needs a maintainer, as it does from the browser, and an approval still cannot approve its author's own change — the tree validator already refused that, and now every transport gets the same answer. The one rule that moved is self-approval for a maintainer's push, which is now refused earlier rather than only at the landing gate. With the branch no longer bundled into `maintain`, CONTRIBUTING and the guides stop telling contributors to use the web. Both transports are one authorization model. Part of #168, stacked on #167, which built the checker this generalises.
docs/architecture.md +8−6
| @@ -27,12 +27,14 @@ There is no password database. The browser performs a WebAuthn ceremony, Vole | |||
| 27 | 27 | stores only the credential's public half, and the resulting session is an opaque | |
| 28 | 28 | token whose hash alone is in `users.db`. | |
| 29 | 29 | ||
| 30 | Browser writes are deliberately narrow: file or comment on an issue, open a | ||
| 31 | change request from a branch the account may push, and approve a change request. | ||
| 32 | An untrusted contributor's Git writes are confined to | ||
| 33 | `refs/heads/contrib/<name>/`; the pre-receive gate enforces the namespace, | ||
| 34 | and CI for that branch does not run until a maintainer allows the exact tip. They are built by the same metadata writers the CLI uses, so | ||
| 35 | the file format and its validation do not fork. A change request opened from a | ||
| 30 | There are two ways to write metadata, and they offer the same account the same | ||
| 31 | actions: a browser session, where each form asks for one capability, and a push, | ||
| 32 | where pre-receive asks the same capabilities path by path. Both are built on the | ||
| 33 | same metadata writers, so the file format and its validation do not fork. An | ||
| 34 | untrusted contributor's Git writes are confined to `refs/heads/contrib/<name>/`, | ||
| 35 | and the `vole` branch it may also write is checked the same way the browser's | ||
| 36 | forms are; CI for a contributor branch does not run until a maintainer allows the | ||
| 37 | exact tip. A change request opened from a | ||
| 36 | 38 | branch follows it: after an accepted push, the server records the new tip as the | |
| 37 | 39 | request's head, so approvals of the old tip stop counting. Everything else still | |
| 38 | 40 | changes by pushing over SSH. Approvals, comments, and CI results belong to the | |