This repository has been archived on 2026-07-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
reuse-surface/specs/FederationHubAPI.md
tegwick e0a4de3310
Some checks failed
ci / validate-registry (push) Has been cancelled
REUSE-WP-0019-T02: hub recompose staleness tracking + Forgejo webhook
reuse_surface/hub/store.py: compose_state table tracking composed_at/stale,
updated only on a *forced* recompose (refresh=true, webhook, future
scheduled fallback) -- a plain GET still serves current best-effort data
but never silently reports itself as freshly composed.

reuse_surface/hub/webhooks.py: constant-time HMAC-SHA256 signature
verification (fails closed on an empty/unconfigured secret) and
path-only push-payload inspection (never parses file content, per design
principle 2 -- webhook only decides whether to trigger a pull-based
recompose).

New endpoint POST /v1/webhooks/forgejo, accepting both
X-Forgejo-Signature and X-Gitea-Signature headers since sibling repos
migrate independently. GET /v1/federated and POST /v1/federated/compose
now share an asyncio.Lock with the webhook so concurrent recompose
triggers coalesce instead of overlapping.

No separate /v1/recompose route was added -- POST /v1/federated/compose
already did that job from earlier hub work; specs/FederationHubAPI.md now
documents this explicitly instead of duplicating it.

28 new pytest cases (128 total pass). Live-verified: ran the actual hub
service locally and sent a real HMAC-signed webhook over HTTP, confirming
the full webhook-to-recompose path, signature rejection, and the
irrelevant-path no-op.

Does NOT deploy to the live reuse.coulomb.social hub -- that needs a
container rebuild/push/k8s rollout, a separate production-deployment
action out of scope here without explicit sign-off.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:24:55 +02:00

10 KiB

Federation Hub API

Repository: reuse-surface
Artifact: specs/FederationHubAPI.md
Status: Draft 0.1 (REUSE-WP-0011-T01)
Schema: schemas/hub-registration.schema.yaml


1. Purpose

The federation hub is a hosted coordination service that records which repositories publish capability indexes and serves a composed federated index for agent discovery. It does not store capability entry Markdown bodies.

Companion deployment workplans: railiance-apps RAILIANCE-WP-0007 (Helm release), RAILIANCE-WP-0008 (browser landing page).

Browser vs API routing

Production ingress (owned by railiance-apps) splits paths:

Path Handler
GET / (HTTPS) Static landing page (reuse-surface-landing) — humans only
GET /health, GET /v1/* Hub API (reuse-surface service)

The landing page does not implement registration or federation; clients and agents should use /health and /v1/* only. See railiance-apps/docs/reuse-surface-on-railiance01.md.


2. Base URL and formats

Item Value
Default production URL https://reuse.coulomb.social (A → 92.205.62.239)
API prefix /v1
Read formats JSON (default), YAML via Accept: application/yaml or ?format=yaml
Write content type application/json

Environment variables for clients:

Variable Purpose
REUSE_SURFACE_URL Service base URL (no trailing slash)
REUSE_SURFACE_TOKEN Bearer token for write operations

3. Authentication

Endpoint class Auth
GET /health, GET /v1/repos, GET /v1/repos/{repo}, GET /v1/federated Public (read)
POST /v1/repos, PATCH /v1/repos/{repo}, DELETE /v1/repos/{repo}, POST /v1/federated/compose Bearer token required

Write requests must include:

Authorization: Bearer <REUSE_SURFACE_TOKEN>

Missing or invalid token → 401 Unauthorized.


4. Registration model

A registration mirrors federation url sources from schemas/federation.schema.yaml, plus hub metadata:

Field Required Notes
repo yes Slug [a-z][a-z0-9-]*; primary key
url yes HTTP(S) URL to capabilities.yaml
enabled yes Include in federated compose when true
domain yes e.g. helix_forge
required no Fail compose if fetch fails and no cache
description no Human-readable note
cache_ttl_seconds no Default 86400
auth_env no Hub container env var for fetch auth (never returned in GET)
auth_header no Default Authorization
registered_at hub ISO-8601 UTC
updated_at hub ISO-8601 UTC
registered_by no Optional client-supplied actor label

Local filesystem index paths are not accepted — registrations must use published raw URLs.


5. Endpoints

5.1 GET /health

Liveness/readiness probe.

Response 200:

{
  "status": "ok",
  "service": "reuse-surface",
  "version": "0.1.0"
}

5.2 GET /v1/repos

List all registrations (including disabled).

Response 200:

{
  "count": 2,
  "repos": [
    {
      "repo": "reuse-surface",
      "url": "https://gitea.coulomb.social/coulomb/reuse-surface/raw/main/registry/indexes/capabilities.yaml",
      "enabled": true,
      "required": true,
      "domain": "helix_forge",
      "description": "Primary registry",
      "cache_ttl_seconds": 86400,
      "registered_at": "2026-06-15T12:00:00Z",
      "updated_at": "2026-06-15T12:00:00Z"
    }
  ]
}

auth_env is omitted from responses.

5.3 POST /v1/repos

Register a new repository. Auth required.

Request body: registration_request from schema.

Response 201: Full registration object.

Errors:

Code Condition
400 Schema validation failure
401 Missing/invalid token
409 repo already registered

5.4 GET /v1/repos/{repo}

Fetch one registration.

Response 200: Registration object.
Response 404: Unknown repo.

5.5 PATCH /v1/repos/{repo}

Update fields on an existing registration. Auth required.

Request body: registration_update from schema (at least one field).

Response 200: Updated registration.

Errors: 400, 401, 404.

5.6 DELETE /v1/repos/{repo}

Remove a registration. Auth required.

Response 204: No content.
Response 404: Unknown repo.

5.7 GET /v1/federated

Return the composed federated index from all enabled registrations.

Reuses WP-0010 remote fetch/cache logic server-side. Output shape matches registry/indexes/federated.yaml:

version: 1
updated: "2026-06-15"
domain: helix_forge
collision_policy: warn
sources:
  - repo: reuse-surface
    url: https://...
    count: 12
capabilities:
  - id: capability.registry.register
    source_repo: reuse-surface
    source_url: https://...
    # ... index fields ...
composed_at: "2026-07-07T16:22:09+00:00"  # REUSE-WP-0019-T02
stale: false                               # REUSE-WP-0019-T02

composed_at/stale (added REUSE-WP-0019-T02) track forced recomposes only (refresh=true, the webhook, or the scheduled fallback) — a plain GET still serves current best-effort data (per-source cache_ttl_seconds still applies) but never silently clears a staleness signal nothing actually refreshed. composed_at is null until the first forced recompose since the hub process's SQLite DB was created. stale: true means a registry/indexes/ change was pushed (via webhook) since the last forced recompose completed.

Query parameters:

Param Default Meaning
format json json or yaml
refresh false Bypass remote cache when true; also updates composed_at and clears stale

Warnings from compose (duplicate IDs, fetch fallbacks) are returned in response header X-Federation-Warnings (semicolon-separated) for MVP; JSON envelope extension is a future option. composed_at is also echoed as response header X-Composed-At when set.

Response 200: Federated index document.
Response 502: Required source unavailable with no cache.

5.8 POST /v1/federated/compose

Trigger federated index refresh (same as GET /v1/federated?refresh=true). Auth required. Useful for operators after bulk registration changes. This is the hub's recompose endpoint referenced elsewhere as "trigger a recompose" — there is no separate /v1/recompose route; this one already does that job.

Response 200: Federated index document (with composed_at updated, stale cleared).

5.9 POST /v1/webhooks/forgejo

Forgejo (or Gitea, during the transition) push-event webhook receiver. Added REUSE-WP-0019-T02. Design: webhook triggers, compose stays pull-based — the webhook never parses pushed file content into registry state; it only decides whether to trigger a pull-based recompose from the already-registered raw URLs.

Signature verification (required, not optional): HMAC-SHA256 over the raw request body, hex-encoded, in X-Forgejo-Signature (or X-Gitea-Signature — both accepted, since Forgejo is Gitea-compatible and repos migrate independently). Secret from REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET (never in code or committed config — route via the standard credential mechanism for this deployment). A missing/wrong signature is 401; an unconfigured secret is 503 (fails closed, not open).

Behavior:

  1. Verify signature (raw body, constant-time compare).
  2. Parse the push-event payload; check every commit's added/modified/removed lists for any path under registry/indexes/.
  3. If none match: 200 {"accepted": false, "reason": "no registry/indexes/ change"} — no-op.
  4. If a match: mark the compose state stale, run a real recompose (refresh=true equivalent) under the same lock used by POST /v1/federated/compose (concurrent triggers coalesce rather than overlap), record composed_at, clear stale.

Response 200: {"accepted": true|false, ...}.
Response 401: Missing or invalid signature.
Response 503: Webhook secret not configured.
Response 502: Recompose failed (stale is deliberately left set so the next check reports the failure honestly, not silently).

Org-level webhook rollout (single config covering all repos, T03) and the Forgejo Actions scheduled fallback for when webhooks are unavailable are separate, deployment-side follow-ups — this section covers the receiver contract only.


6. Error envelope

Non-2xx responses use:

{
  "error": "validation_error",
  "message": "Human-readable summary",
  "details": ["optional field-level messages"]
}
error code HTTP
validation_error 400
unauthorized 401
not_found 404
conflict 409
misconfigured 503
compose_error 502

7. Hub service configuration

Env var Required Purpose
REUSE_SURFACE_TOKEN yes Write API bearer token
REUSE_SURFACE_DB no SQLite path (default /data/reuse.db)
REUSE_SURFACE_CACHE_DIR no Remote index cache (default /data/cache)
REUSE_SURFACE_DOMAIN no Default federated domain (default helix_forge)
REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET for webhook HMAC secret for POST /v1/webhooks/forgejo (REUSE-WP-0019-T02)
REUSE_SURFACE_FORGE_BASE_URL no Default target host for reuse-surface federation migrate-host (REUSE-WP-0019-T01), e.g. https://forgejo.coulomb.social

8. CLI mapping

CLI command API call
reuse-surface hub status GET /health
reuse-surface hub list GET /v1/repos
reuse-surface hub show --repo X GET /v1/repos/X
reuse-surface hub register ... POST /v1/repos
reuse-surface hub update ... PATCH /v1/repos/{repo}

Run locally: reuse-surface serve. Global client flags: --base-url, env REUSE_SURFACE_URL, REUSE_SURFACE_TOKEN.


9. Deployment reference

  • Image: gitea.coulomb.social/coulomb/reuse-surface:<tag>
  • Public URL: https://reuse.coulomb.social
  • Secret: reuse-surface-env with REUSE_SURFACE_TOKEN
  • Probe path: /health
  • Persistence: PVC at /data (SQLite + fetch cache)
  • Helm release: railiance-apps RAILIANCE-WP-0007
  • Landing page at /: railiance-apps RAILIANCE-WP-0008 (disable via landing.enabled: false in Helm values)