vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Store CI run records in the database, not the repository
b312e1660ac3d570fc35350a22b3514c56ab466f · Keenan Nemetz · 11d ago · parent db5e4382
Store CI run records in the database, not the repository A finished attempt was a set of files on the vole branch: a record, step ranges, and artifact pointers, pushed as a commit. At 959 files, CI records were 849 of them, and every human metadata write had to carry them. The runner now sends the record with its finish message over the SSH channel that holds the lease. The server checks the account, the job, and the log and artifact pointers, then writes the attempt, steps, artifacts, attestation, and run result in one transaction in data/runs.db. A tagged build still pushes its release pointers to the branch after the record is stored. Pre-receive refuses a new runs/ file. vole admin index rebuild imports records still on an old branch; records written since are never deleted by a rebuild. The run page reads the stored attempt, and runs.db is now a source that must be backed up. This stops new CI history from entering Git. Existing commits keep the records they already have; removing those would require rewriting the protected vole branch, which this change does not do.
docs/architecture.md +18−13
| @@ -58,17 +58,18 @@ auditable. | |||
| 58 | 58 | | Data | Where it lives | Rebuildable? | | |
| 59 | 59 | |---|---|---| | |
| 60 | 60 | | Repositories | `data/repos/<name>.git` | It is the source | | |
| 61 | | Issues, requests, approvals, release pointers, CI records | The `vole` branch in each repository | It is the source | | ||
| 61 | | Issues, requests, approvals, release pointers | The `vole` branch in each repository | It is the source | | ||
| 62 | | CI records: finished attempts, steps, artifact pointers, and who reported them | `data/runs.db` | It is the source; back it up | | ||
| 62 | 63 | | Accounts, instance roles, SSH keys, passkeys, enrollment links, sessions, access rules, locks, page-view counters | `data/users.db` | No; back it up | | |
| 63 | 64 | | Web/search index, including the `#42` alias numbering | `data/index.db` | Yes, with `vole admin index rebuild` | | |
| 64 | | CI queue, run index, contributor CI approvals | `data/runs.db` | Partly; in-progress work and approvals are lost | | ||
| 65 | | CI queue, in-progress runs, contributor CI approvals | `data/runs.db` | Partly; in-progress work and approvals are lost | | ||
| 65 | 66 | | LFS files, CI logs, build artifacts | `data/lfs/` | No; stored by content | | |
| 66 | 67 | | Privileged-action history | `data/audit.log` | It is the record; a privileged change that cannot be written there is refused | | |
| 67 | 68 | | SSH host key | `data/ssh_host_ed25519_key` | No; it is the secret | | |
| 68 | 69 | ||
| 69 | Git holds the repository history. SQLite holds the account database, the CI queue, | ||
| 70 | and generated indexes. Anything that can be rebuilt comes from Git or stored files. | ||
| 71 | Anything that cannot be rebuilt is small and easy to back up. | ||
| 70 | Git holds the repository history. SQLite holds the account database, the CI queue | ||
| 71 | and records, and generated indexes. Anything that can be rebuilt comes from Git or | ||
| 72 | stored files. Anything that cannot be rebuilt is small and easy to back up. | ||
| 72 | 73 | ||
| 73 | 74 | The `vole` branch contains: | |
| 74 | 75 | ||
| @@ -79,16 +80,20 @@ requests/<ulid>/request.md | |||
| 79 | 80 | requests/<ulid>/comments/<ulid>.md | |
| 80 | 81 | requests/<ulid>/approvals/<account-name>.md | |
| 81 | 82 | releases/<tag>/<artifact> | |
| 82 | runs/<run-id>/<attempt>/record.md | ||
| 83 | runs/<run-id>/<attempt>/log | ||
| 84 | runs/<run-id>/<attempt>/steps/<n>.md | ||
| 85 | runs/<run-id>/<attempt>/artifacts/<name> | ||
| 86 | 83 | ``` | |
| 87 | 84 | ||
| 88 | People write the issue and request files. Vole writes the run records and moves | ||
| 89 | the head of a change request that follows a branch: post-receive derives the new | ||
| 90 | head from the accepted ref and validates the request tree before committing it. | ||
| 91 | Everything that is pushed is checked at the boundary. | ||
| 85 | People write the issue and request files. Vole moves the head of a change request | ||
| 86 | that follows a branch: post-receive derives the new head from the accepted ref and | ||
| 87 | validates the request tree before committing it. A tagged build that publishes | ||
| 88 | release artifacts writes those pointers the same way. Everything that is pushed is | ||
| 89 | checked at the boundary. | ||
| 90 | |||
| 91 | Run records are not on the branch. A runner sends its result over the authenticated | ||
| 92 | SSH channel that leased the job, and the server stores it in `data/runs.db`; a clone | ||
| 93 | or backup of a repository does not carry CI history. An instance upgraded from an | ||
| 94 | older build may still have historical `runs/` files in its `vole` branch history; | ||
| 95 | `vole admin index rebuild` imports them into the database, and new results never go | ||
| 96 | there. | ||
| 92 | 97 | ||
| 93 | 98 | ## Why these choices | |
| 94 | 99 | ||