@@ -174,6 +174,33 @@ between minor releases. |
| 174 | 174 | `repo:read`. Empty repos return `[]`; the single-commit GET |
| 175 | 175 | accepts any unambiguous SHA prefix. |
| 176 | 176 | - **Capability:** `commits` added to `/api/v1/meta`. |
| 177 | +- **GPG keys + commit signature verification (S51).** Full |
| 178 | + vertical: upload OpenPGP public keys at |
| 179 | + `Settings → SSH and GPG keys`; shithub parses the armored |
| 180 | + block (RSA≥2048, ECC, multi-subkey, encryption-only all |
| 181 | + accepted) and stores primary + subkey fingerprint index. A |
| 182 | + background worker eagerly backfills verification rows for the |
| 183 | + uploader's existing commits across every repo, and the |
| 184 | + `shithubd gpg-backfill-all` admin subcommand performs the |
| 185 | + once-off bulk walk on deploy. Signed commits + annotated tags |
| 186 | + render a green **Verified** pill on the commit list, |
| 187 | + single-commit, and tag list pages; the popover surfaces the |
| 188 | + signer, key id, and verified-at timestamp. The `reason` enum |
| 189 | + mirrors GitHub's documented set (`valid`, `unsigned`, |
| 190 | + `unknown_key`, `bad_email`, `unverified_email`, `expired_key`, |
| 191 | + `not_signing_key`, `malformed_signature`, `invalid`). REST |
| 192 | + surface: `GET/POST/DELETE /api/v1/user/gpg_keys[/{id}]` with |
| 193 | + the gh-exact JSON shape (split `can_encrypt_comms` / |
| 194 | + `can_encrypt_storage`, both `public_key` and `raw_key`, |
| 195 | + subkeys-as-nested-objects, `emails[].verified` cross-checked |
| 196 | + against the user's verified-email set). Scopes: `user:read` |
| 197 | + for GETs, `user:write` for mutations. Existing |
| 198 | + `/api/v1/repos/{o}/{r}/commits[/{sha}]` responses now carry a |
| 199 | + `verification` object with the same shape gh emits. |
| 200 | + Migrations: `0066_user_gpg_keys.sql`, |
| 201 | + `0067_user_gpg_subkeys.sql`, |
| 202 | + `0068_commit_verification_cache.sql`. |
| 203 | +- **Capability:** `gpg-keys` added to `/api/v1/meta`. |
| 177 | 204 | - **REST: repo contents (S50 §12).** |
| 178 | 205 | `GET /api/v1/repos/{o}/{r}/contents/{path}[?ref=]` returns |
| 179 | 206 | either a directory listing (dirs first, then files |