feat: publish classifications from Forgejo

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
This commit is contained in:
tegwick 2026-09-01 01:39:39 +02:00
parent 738d407bb7
commit fd624021ec
12 changed files with 2000 additions and 47 deletions

View file

@ -12,11 +12,12 @@ classification, Git revision, source fingerprint, and observation time. Local
paths, remotes, work records, source bodies, and credentials never enter the
envelope.
## Private operator registry
## Forgejo-backed registry
Publication joins repository-owned `.repo-classification.yaml` files to a
private operator registry. The registry is local runtime state because checkout
paths are host-specific; do not commit it. Its schema is:
stable repository registry. Production uses the committed
`config/repository-registry.yaml`; it contains registrar UUIDs and canonical
Forgejo identities, but no credentials or host paths:
```yaml
schema: repo-manager.repository-registry.v1
@ -24,7 +25,7 @@ repositories:
- repository_id: a9d105c6-10fa-4cf9-8bc1-e248476698d3
slug: repo-manager
lifecycle: active
path: /home/operator/repo-manager
forgejo_repository: coulomb/repo-manager
```
Bootstrap current stable identities from the retiring primary State Hub:
@ -33,6 +34,7 @@ Bootstrap current stable identities from the retiring primary State Hub:
rmgr publisher import-registry \
--root /home/operator \
--api-base http://127.0.0.1:8000 \
--source forgejo \
--output ~/.repo-manager/repository-registry.yaml
```
@ -44,9 +46,9 @@ unregistered checkouts are retained as warnings and omitted rather than being
assigned invented identities. Any other failure prevents replacement of the
registry file.
After repository-registry authority moves fully into Repo Manager, normal
governed registration maintains this same private schema and the bootstrap
command can be retired. The publisher contract does not change.
The import is a migration/bootstrap path. Normal governed registration must
maintain the committed UUID-to-Forgejo mapping after State Hub registrar
retirement. The publisher contract does not change.
## Run
@ -56,12 +58,21 @@ Set runtime configuration through the environment:
REPO_MANAGER_REGISTRY_PATH=~/.repo-manager/repository-registry.yaml
REPO_MANAGER_CURSOR_SECRET=<at-least-32-byte-secret>
REPO_MANAGER_API_TOKEN=<optional-bearer-token>
REPO_MANAGER_FORGEJO_BASE_URL=https://forgejo.coulomb.social
REPO_MANAGER_FORGEJO_TOKEN_FILE=/var/run/secrets/repo-manager/forgejo/token
REPO_MANAGER_FORGEJO_WORKERS=8
REPO_MANAGER_FORGEJO_TIMEOUT_SECONDS=10
REPO_MANAGER_FORGEJO_RETRIES=2
REPO_MANAGER_PROJECTION_PAGE_SIZE=100
REPO_MANAGER_PROJECTION_MAX_SNAPSHOTS=4
```
Secret values belong in the platform credential path and must not be committed
or logged. Start the API with:
The Forgejo token is optional for public repositories and required for private
repositories. It must have only `read:repository`; mount it from the platform
credential path rather than putting it in an environment value, Git, or logs.
The cursor secret may be omitted for a single-replica runtime, in which case a
fresh process-local key is generated and in-flight cursors expire on restart.
Start the API with:
```bash
rmgr publisher api --host 0.0.0.0 --port 8020
@ -79,10 +90,12 @@ carry snapshot ID, offset, and page size protected by HMAC-SHA256. Only a small
bounded set of in-flight snapshots is retained; an invalid, tampered, or
expired cursor returns `409`.
Missing checkouts, invalid classifications, or unavailable Git revisions are
emitted as bounded error diagnostics. Hub-core rejects any such transfer and
keeps the last accepted generation, so a partial observation never becomes the
active navigation projection.
For each repository the publisher reads metadata, resolves the default branch
to an exact commit SHA, and then reads `.repo-classification.yaml` at that SHA.
Missing/private repositories, invalid classifications, or unavailable
revisions are emitted as bounded error diagnostics. Hub-core rejects any such
transfer and keeps the last accepted generation, so a partial observation
never becomes the active navigation projection.
## Hub-core client
@ -108,6 +121,7 @@ make publisher-snapshot \
CURSOR_SECRET='<runtime-secret>'
```
RMGR-WP-0013's live conformance used five pages of 25 rows. Hub-core accepted
all 123 registered classifications atomically and produced one current
generation; no State Hub classification table was read by the publisher.
The workstation conformance accepted all 123 local classifications. Forgejo
conformance resolves 114 public repositories without credentials and correctly
rejects the nine private `rapp-*` sources until a least-privilege source-read
token is mounted. No State Hub classification table is read at runtime.