repo-manager/docs/repository-classification-publisher.md
tegwick 9352cc0ea2 Package corrected publisher registry and complete prefix registrations
Record the verified publisher image digest and track wrapper rollout separately. Register existing CORE-WP, DISCTL-WP, and REPO-WP owners and retain the outstanding instruction correction.

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a100de-2e1c-7e23-9ae9-2da0e0499411
2026-10-03 10:33:26 +02:00

5.9 KiB

Repository classification publisher

Repo Manager publishes the frozen helixforge.repository-classification-projection 1.0.0 envelope at:

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:

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:

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.

Deliver registry corrections

Containerfile copies the committed registry into the publisher image. A Git push or rmgr sync updates source/work-record projections; it does not refresh the registry in an already running publisher. After a repository rename:

  1. Verify the canonical Forgejo full_name and preserve the registrar UUID.
  2. Update the registry coordinates, commit, and build an image from that clean commit with make container-build IMAGE_TAG=<full-commit-sha>.
  3. Verify the embedded registry and observe the renamed sources from the image, then push the image and record its immutable digest.
  4. Track the deployment in the consuming wrapper (rapp-core-hub). Pin the new digest and deploy through its approved release procedure, then run make verify-publisher before enabling publisher.connectCandidate.

Keep the exact identity check strict: an old Forgejo URL redirecting to a new repository name is evidence to update the registry, not permission to silently change published identity. Source correction and live rollout need separate completion evidence. The October 3 refresh is tracked in RMGR-WP-0018.

Run

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

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:

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:

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

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.