gitea: let an agent put a commit on a branch of its own, and read history, compares, reviews, directories and code (issue 375)
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered

An agent could not get a finished change onto the forge without someone
pushing by hand, so the branch / pull request / approval rule could not be
followed. A Go bundle serves gitea_commit, held to gitea_api's rule (never the
default branch, never a protected one), and the summarised reads agents
otherwise made through gitea_api as raw JSON.
This commit is contained in:
jochen
2026-10-12 12:06:23 +02:00
parent da296cacc6
commit 9eb2ea8d69
8 changed files with 1972 additions and 1 deletions
+41 -1
View File
@@ -2,7 +2,7 @@
The forge, and the mesh's npm package registry: it claims the `git` and `npm-package-registry` seats.
Its tools, its events and the provisioner of registry accounts are the TypeScript bundle (`index.ts`,
`tools/`, `provisioner/`). This README covers the Go bundle beside it.
`tools/`, `provisioner/`). This README covers the two Go bundles beside it.
## Announcing pull requests to the merge check (novox/hq to-be 45 §9, issue 347)
@@ -28,6 +28,46 @@ Every new head of an open pull request is announced as `pull.updated`, once, and
- **A pull request or issue number that is not one is refused** before the forge is asked: a tool called with a
misnamed argument sent `pulls/NaN`.
## An agent's commit and the reads around it (novox/hq issue 375)
An agent that finished a change on a branch had no way to put it on the forge: the module had no commit or
push tool, `gitea_api` refused the forge's patch endpoint, and its only file write took one whole file
base64-encoded. The Go bundle `cmd/forge` serves the commit, and the reads an agent otherwise made through
`gitea_api` as raw JSON, summarised.
| tool | | what |
|---|---|---|
| `gitea_commit` | a | one commit on a branch of its own, created from `base` (default the default branch) when absent, carrying either `files` (each `{path, content \| content_base64 \| delete: true}`) or `patch` (a unified diff, as `git diff` writes it); answers the commit and its parent |
| `gitea_commits` | r | a ref's history, newest first, optionally only commits touching a path: sha, author, date, subject |
| `gitea_compare` | r | what head holds that base does not: its commits and the files they touch |
| `gitea_pull_reviews` | r | a pull request's reviews — state, the commit reviewed and whether it is the head now, stale, dismissed — with their line comments |
| `gitea_list_dir` | r | a directory's entries at a ref: name, type, a file's size |
| `gitea_search_code` | r | the lines of a ref's text files matching a literal string or a regular expression, optionally under a path |
**Where a commit may go** is the rule `gitea_api` already holds for a file written (novox/hq ADR 0266):
never the repository's default branch, never a branch a protection rule names (a glob rule covers by the same
matcher as `guard.ts`). The rules are read before anything is sent; rules that cannot be read refuse the
commit. Nothing about merging changes: a merge still needs an independent review at the head and both
required checks green, through `gitea_merge_pull_request`.
**How the commit is made.** `files` become one call to the forge's multi-file contents endpoint: a path the
ref holds is updated, one it does not is created, and an update or delete carries the blob read just
before, so a file someone changed meanwhile is refused by the forge rather than overwritten. A `patch` goes
to the forge's diff endpoint, which applies it; for a small change to a large file this is far shorter than
the file. Either way the commit is the forge's own, made as the forge's admin account, so its sha is not the
local commit's: the merge check judges the head on the forge. After the write the branch is read back, and a
branch not at the commit the forge named is a failure, never a success.
**Code search** has no endpoint on the forge, so the ref's archive (`/archive/{ref}.tar.gz`, at most 64 MiB)
is read once and searched in memory; a binary file is passed over, a file over 4 MiB is named under
`skipped`, and at most `max` matches (default 100) are answered, with `truncated` when more were found.
It reaches the forge as the npm-registry bundle does (below), and one tool call takes at most 50 seconds.
`go test ./cmd/forge` runs it against a fake forge: the files of one commit created, updated and deleted on
a new branch from its base; an existing branch committed on; the default branch, a protected branch and
unreadable rules refused before anything is sent; a patch sent to the forge; a branch read back at another
commit failing; every malformed ask refused; and each read summarised.
## The package registry's retention (novox/hq ADR 0251 §4, to-be 51)
Every publish adds a version to the registry, and until this nothing removed one. The Go bundle