a73x

132d9eca

Governance: close three gaps in the exposure model

a73x   2026-08-11 18:07

Commit message
Governance: close three gaps in the exposure model

Writing the model up left three things an implementer would have to
guess, all now decided:

- `option listed = yes` without `R = @anonymous` is a config error. The
  "listed only to viewers who can read it" reading is meaningless
  because web UI auth is out of scope, so HTTP has no identity and the
  pair would advertise a name that 404s.
- The inverted default applies only where governance is in force. A
  server with no settings.git keeps today's behaviour; flipping it
  globally would silently hide every repo on every existing deployment.
- Release downloads follow the repo's anonymous read grant, for the
  same reason a clone does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

docs/superpowers/specs/2026-08-09-repo-governance-design.md
Old New
@@ -237,11 +237,30 @@ is an access decision, resolved by the same first-match-wins evaluation as any
237 other — including `-` to deny it back. `listed` is an **option**, not a rule, 237 other — including `-` to deny it back. `listed` is an **option**, not a rule,
238 because advertising is a display concern rather than an access one; this is the 238 because advertising is a display concern rather than an access one; this is the
239 `option` line gitolite already has, so it is stolen rather than invented. 239 `option` line gitolite already has, so it is stolen rather than invented.
240 `listed` implies nothing about readability: a repo listed but not 240 Three consequences that are easy to get wrong, and each of which an implementer
241 `R = @anonymous` appears only to viewers who can read it. 241 would otherwise have to guess:
242 242
243 `@anonymous` must never satisfy a write grant. `RW = @anonymous` is a config 243 **`option listed = yes` requires `R = @anonymous`, and is a config error
244 error and is rejected by push validation, not silently ignored. 244 without it.** The tempting reading — "listed, but only to viewers who can read
245 it" — is meaningless here: web UI authentication is explicitly out of scope
246 below, so HTTP has no identity and there is no authenticated web viewer to show
247 it to. Listing a repo nobody may read would advertise a name that 404s. Reject
248 the pair at push validation rather than rendering it.
249
250 **`@anonymous` never satisfies a write grant.** `RW = @anonymous` is a config
251 error, rejected by push validation, not silently ignored.
252
253 **The inverted default applies only where governance is in force.** A server
254 with no `settings.git` keeps today's behaviour exactly — that is already an
255 asserted requirement (`tests/governance_test.rs`, the `ungoverned` harness) and
256 flipping the default globally would silently hide every repository on every
257 existing deployment the moment it upgraded. The default is a property of the
258 governed world, not of the binary.
259
260 **Release downloads follow the repo's anonymous read grant.** They are served
261 over HTTP from the repo's gitdir, so `R = @anonymous` governs them for the same
262 reason it governs a clone. Publishing and deleting stay `RW+` and remain
263 SSH-only.
245 264
246 ### Bootstrap, corrected 265 ### Bootstrap, corrected
247 266