repo-manager/docs/repository-classification-publisher.md
tegwick fd624021ec feat: publish classifications from Forgejo
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
2026-09-01 01:39:39 +02:00

127 lines
4.8 KiB
Markdown

# Repository classification publisher
Repo Manager publishes the frozen
`helixforge.repository-classification-projection` 1.0.0 envelope at:
```text
GET /ports/repositories/classifications?cursor=<opaque>
```
The route is read-only. It contains registrar UUID, slug, lifecycle,
classification, Git revision, source fingerprint, and observation time. Local
paths, remotes, work records, source bodies, and credentials never enter the
envelope.
## Forgejo-backed registry
Publication joins repository-owned `.repo-classification.yaml` files to a
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
repositories:
- repository_id: a9d105c6-10fa-4cf9-8bc1-e248476698d3
slug: repo-manager
lifecycle: active
forgejo_repository: coulomb/repo-manager
```
Bootstrap current stable identities from the retiring primary State Hub:
```bash
rmgr publisher import-registry \
--root /home/operator \
--api-base http://127.0.0.1:8000 \
--source forgejo \
--output ~/.repo-manager/repository-registry.yaml
```
The import verifies `instance_role=primary`, discovers only classified Git
working copies below the selected root, and uses bounded direct identity reads.
It does not use State Hub's expensive bulk repository projection and does not
trust State Hub host paths. Transient reads retry; locally classified but
unregistered checkouts are retained as warnings and omitted rather than being
assigned invented identities. Any other failure prevents replacement of the
registry file.
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
Set runtime configuration through the environment:
```text
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
```
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
```
`GET /healthz` is process liveness. `GET /readyz` verifies that configuration
and the registry are usable. When `REPO_MANAGER_API_TOKEN` is set, the
projection route requires the matching bearer token.
## Snapshot and failure semantics
A first-page request observes the complete registry into an immutable in-memory
snapshot. Repositories are ordered by registrar UUID. Subsequent page cursors
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`.
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
Configure the hub-core runtime with:
```text
HUB_CORE_REPO_MANAGER_BASE_URL=http://repo-manager:8020
HUB_CORE_REPO_MANAGER_API_TOKEN=<same-optional-bearer-token>
HUB_CORE_REPO_MANAGER_TIMEOUT_SECONDS=10
HUB_CORE_REPO_PROJECTION_REFRESH_SECONDS=300
```
Hub-core refreshes once at startup and then on the configured interval. A
failed refresh marks the dependency stale while preserving the last accepted
generation. Setting the refresh interval to `0` disables the background loop.
## Verification
```bash
make test
make publisher-snapshot \
REGISTRY=~/.repo-manager/repository-registry.yaml \
CURSOR_SECRET='<runtime-secret>'
```
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.