Files
mesh-catalog/modules/gitea
..

gitea

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 two Go bundles beside it.

Announcing pull requests to the merge check (novox/hq to-be 45 §9, issue 347)

Every new head of an open pull request is announced as pull.updated, once, and the controller has it checked.

  • Which repositories a pass looks at. Each that moved since the last pass, less a minute's margin (issue 250); each holding a pull request updated since then, found by the forge's own search — opening, reopening or retargeting a pull request moves its own update time and not its repository's, and a pull request opened more than a minute after its push was never announced (issue 347); and every repository on a sweep, every ten minutes, or when the search fails.
  • A pull request nobody checks is said — by the delivery's owner, not here. gitea_open_pulls answers every open pull request from the forge itself, with the statuses its base requires and the ones its head has. The mesh-delivery module asks it every five minutes, and a head on a base requiring mesh/merge-gate or mesh/repo-check with neither for ten minutes since it first saw it so is a delivery held past its bound (delivery.<id>.stalled, state unannounced), which the controller raises like any other (issue 347). Each of the head's statuses comes with its state and when it was set, so a head whose check started and never answered — a required check pending, or never set beside one that is — is said as a stalled line in the state unanswered once that has lasted the delivery table's bound for a check asked and not answered, an hour, counted from its first sight and again only when a check is asked anew (issue 438).
  • How to see it work. A pull request opened more than a minute after its branch was pushed — this fix's own was one, and the announcer it fixes never saw it — has mesh/merge-gate pending within a pass of the next sweep at the latest, and mesh-controller.conditions names none for it.
  • 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 cmd/npm-registry says which versions each package holds, what keeps each one, and — asked with a why — deletes the versions nothing keeps.

tool what
npm_packages r every package of the registry's owner, each version newest first with its publish time, its size and why it is kept
npm_retention a the versions retention would delete, and the bytes. A dry run unless dry_run is false; a real run needs why

A version is kept when:

  • a lockfile on the default branch of any repository on the forge names it — package-lock.json (every lockfile version), npm-shrinkwrap.json, yarn.lock (classic and berry) and pnpm-lock.yaml (by each resolved name@version or name/version), whatever the version's age;
  • it is the highest published version satisfying a range that a package.json there names in its dependencies, development, peer or optional dependencies (an npm: alias counts for the package it stands for; a path, a URL, git or a workspace is not a range on this registry). npm itself installs the latest dist-tag when it satisfies the range; that version is kept by its dist-tag;
  • a dist-tag names it — and a range that is a dist-tag's name keeps that tag's version;
  • it is among the newest keep of its package, five by default (the artifact store's five builds, ADR 0189 §3), by semantic-version order. Pre-releases count among the newest.

A version that is not a semantic version is never ordered, so it is kept. A range that cannot be read, or a package whose dist-tags cannot be read, keeps every version of its package, and the answer says why under keeps_all.

Nothing is deleted on partial knowledge. A repository whose files could not be listed, or a manifest or lockfile that could not be fetched, is named under repositories_unread in every answer, and a real run then deletes nothing at all. A file that was fetched but is not readable (broken JSON) is named under files_not_read, and so is a lockfile entry of a shape the reader does not know (the rest of that file is still read); what it would have named is not known, so a real run then deletes nothing at all either. Under node_modules nothing is read: that is what a lockfile already says. An empty repository, or one without its default branch, holds nothing to read and is not a failure.

A real run deletes each version through the forge's own interface (DELETE /api/v1/packages/{owner}/npm/{name}/{version}), says what it deleted and what the forge refused, and a version already gone counts as deleted.

How it reaches the forge

Over the forge's HTTP interface on the machine (MESH_GITEA_URL), as the admin account the vault delivered (MESH_GITEA_ADMIN_USER, the password in MESH_GITEA_ADMIN_PASSWORD_FILE — the same file the TypeScript bundle mints its token with), by basic authentication: this bundle mints no token and keeps nothing. The password is read per call and never logged, answered or put in an error. The registry's owner is MESH_NPM_OWNER, the owner in the seat's npm-path.

Calls to the forge run eight at a time, and one tool call reads for at most 50 seconds, inside a module's 60-second ask.

Why the manifest lists no tools

The TypeScript bundle's tools are served without a tools list, and a claim without serves offers the module's tools as the seat's verbs — so a list here would make these two tools verbs of both the git and the npm-package-registry seats. The two names are kept in ToolNames, and a test holds them to this README and to what the bundle serves.

Tests

go test ./...

Against a fake forge (httptest): what keeps a version (a lockfile whatever its age, the highest version satisfying a range, a dist-tag, the newest keep); a dry run deleting nothing; a real run without why refused; a real run deleting only what nothing keeps; an unread repository stopping a real run before anything is deleted; an unreadable range keeping its whole package; a wrong password failing without saying the password. And the range matcher against npm's rules — exact, ^, ~, x and *, comparisons with partial versions, hyphen ranges, ||, and the pre-release rule — and each lockfile format.