feat: publish repository classification projections

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
This commit is contained in:
tegwick 2026-09-01 00:48:32 +02:00
parent 90e8d78ad3
commit 54271a2261
8 changed files with 1327 additions and 1 deletions

View file

@ -0,0 +1,64 @@
# RMGR-WP-0013 live publisher conformance — 2026-09-01
## Scope
This evidence covers the Repo Manager classification publisher and hub-core's
frozen `helixforge.repository-classification-projection` 1.0.0 consumer. It is
a live source/conformance proof from the workstation checkout fleet, not a
production deployment claim.
## Registrar bootstrap
- State Hub source: `primary` / `railiance01`
- Discovery root: `/home/worsch` (operator-local, not published)
- Registered classified repositories: **123**
- Locally classified but unregistered checkouts: **2**
(`snuggles-inventor`, `wise-validator`), reported as warnings and omitted
- Stable IDs: existing State Hub registrar UUIDs; none invented
- Bulk `/repos/` was not used because it timed out after 180 seconds
- Successful bounded import: four workers, 15-second request timeout, two
transient retries
The generated private registry was written only to `/tmp` and was not committed.
It contained host paths for local observation; those paths did not enter the
published projection.
## End-to-end transfer
The publisher served the live registry with a page size of 25. Hub-core fetched
five HMAC-bound pages through its new HTTP `port.repo` client and accepted the
complete transfer atomically.
```json
{
"status": "accepted",
"snapshot_id": "c22faf46794ee9288102d6f0f98b04372ebfaa2c41017edf11c6101f157ec654",
"repository_count": 123,
"projection_status": "current",
"content_hash": "a3a1d584c8245b6c339bd672551d0960bf2b0b133e259bec7e884fe302cfe14c",
"source_revision": "b28a45e7c9592f6aecdc4537b463333545ba6f1c68746d6a5e904cff4d654008"
}
```
Repo Manager read classification from each repository's authoritative file and
published only contract fields. Hub-core derived its own navigation generation;
no database or private persistence model was shared.
## Verification
- Repo Manager: **156 passed**; Ruff clean
- Hub-core: **107 passed**
- Focused publisher tests cover duplicate identities, deterministic UUID order,
Git/source provenance, multi-page transfer, cursor tampering, invalid source
diagnostics, bearer authentication, readiness, and primary registrar import
- Hub-core focused tests cover cursor/token HTTP transport plus all frozen
repository-navigation contract and ingestion behaviors
## Remaining production gate
The code and cross-repository transport are ready. Production still needs an
explicit placement/network decision because hub-core runs in the public Core
Hub cluster while the authoritative checkout registry is host-local to the
Repo Manager worker. No broad host-path mount or implicit State Hub dependency
was introduced to hide that boundary. `RMGR-WP-0013-T05` remains waiting for
that deployment handoff; `HUB-WP-0006-T06` must not switch traffic before it.

View file

@ -0,0 +1,113 @@
# 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.
## Private operator 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:
```yaml
schema: repo-manager.repository-registry.v1
repositories:
- repository_id: a9d105c6-10fa-4cf9-8bc1-e248476698d3
slug: repo-manager
lifecycle: active
path: /home/operator/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 \
--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.
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.
## 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_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:
```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`.
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.
## 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>'
```
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.