Implements the hub side of the shared reuse-event schema (already drafted in WP-0018-T01, schemas/reuse-event.schema.json): a SQLite reuse_events table, POST /v1/reuse-events (token-auth), GET /v1/reuse-events?capability_id= (read-only). reuse_surface/plan_check.py: refactored record_outcome around a new shared post_or_fallback_reuse_event() helper -- tries the hub first, falls back to the local JSONL only on failure/unreachability, never both. New record_manual_reuse_event() backs a new CLI command, reuse-surface record-reuse, for retroactive facts recorded outside plan-check. Privacy/scope (repo slugs and capability ids only, no code, no secrets) is enforced structurally via the schema's additionalProperties: false, not just by convention. 21 new pytest cases, 145 total pass. Live-verified against a real running hub instance: POST/GET /v1/reuse-events directly, record-reuse and plan-check --record-outcome both posting successfully to the hub, and -- after actually killing the hub process -- confirmed the fallback path writes correctly to the local JSONL instead of erroring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id | reuse_check |
|---|---|---|---|---|---|---|---|---|---|---|---|
| REUSE-WP-0019 | workplan | Forgejo-native federation automation and reuse telemetry | infotech | reuse-surface | active | claude-code | helix-forge | 2026-07-06 | 2026-07-07 | 569be717-34f8-4039-bb26-497685f60159 | new — dogfooded 2026-07-07 via reuse-surface plan-check against the full 61-capability federated index; no existing capability covers Forgejo webhook automation or reuse telemetry |
Forgejo-native federation automation and reuse telemetry
Federation compose is on-demand and the hub serves whatever was last composed; roster sweeps are manual; reliability evidence is structural (CI exists) rather than observed (someone reused it and it worked). This workplan makes the registry live (event-driven recompose) and evidence-backed (reuse telemetry feeding the R axis).
Platform constraint: the forge is transitioning Gitea → Forgejo. All
new automation attaches to Forgejo (webhooks, Forgejo Actions, API tokens) —
nothing new is built against Gitea. Existing raw URLs
(https://gitea.coulomb.social/...) and .gitea/workflows/ must migrate or
be made host-agnostic. Forgejo is Gitea-API-compatible, so migration is mostly
host/path configuration, but every hardcoded gitea. reference is a liability.
Depends on: REUSE-WP-0017 (content worth refreshing), REUSE-WP-0018-T01 telemetry schema (shared). Closes SCOPE "not possible yet" item automatic hub refresh and moves reliability evidence beyond structural.
Design principles
- Host-agnostic first — a single
forge_base_urlconfiguration (env/config + hub setting) replaces hardcoded hosts; the Forgejo cutover becomes a one-line change per surface. - Webhook triggers, compose stays pull-based — the webhook only marks the hub's composed index stale and triggers recompose from published raw URLs; no push-parsing of payloads into registry state.
- Degrade to schedule — if webhooks are unavailable, a scheduled Forgejo Actions job recomposes on an interval; freshness is monitored either way.
- Telemetry is append-only and low-ceremony — reuse events are JSONL
facts (who consumed what, when, outcome); aggregation derives
reused_byrelations and R-axis evidence citations, never hand-edited.
Dependencies
| Dependency | Owner | Notes |
|---|---|---|
| Forgejo instance + admin | Bernd / infra | webhook config, org-level token, Actions runners |
| Gitea→Forgejo cutover plan | infra | final hostname, raw URL scheme, redirect window |
| Hub deployment (reuse.coulomb.social) | reuse-surface / railiance | new endpoint + config rollout |
| REUSE-WP-0018-T01 | reuse-surface | shared telemetry/outcome schema |
| plan-check adoption | ecosystem | telemetry volume comes from WP-0018-T05 rollout |
Forge Host Abstraction And URL Migration Inventory
id: REUSE-WP-0019-T01
status: done
priority: high
state_hub_task_id: "4a187b56-bff9-4097-abd0-b423e7bf9442"
Inventory findings (2026-07-07): cross-referenced sources.yaml (61
entries) against each repo's actual git origin remote. Result: 11 repos
already migrated their remote to Forgejo; 50 still on Gitea (correctly, no
action needed). Of the 11 Forgejo-origin repos, 9 already had correct
sources.yaml entries (from WP-0017 drafting/registration); 2 were
stale — activity-core and state-hub — still pointing at their old
Gitea raw URL despite having migrated. This was real, live debt, not a
hypothetical: both were confirmed reachable on Forgejo (HTTP 200) before
being migrated for real, in both sources.yaml and the production hub
registration (hub update --url). Verified post-migration against
GET /v1/federated — both now show forgejo.coulomb.social.
config-atlas's WP-0017-T06 303 was not a host-transition symptom —
already diagnosed there as (a) a redirect the current code already follows
fine, and (b) a hub-registration gap unrelated to host. No new finding here.
Implemented:
reuse_surface/forge_host.py:parse_raw_url/derive_raw_url(handles both the legacy/raw/<branch>/...form and the canonical/raw/branch/<branch>/...form both forges serve without a 303 redirect),rewrite_url_host,forge_base_url(readsREUSE_SURFACE_FORGE_BASE_URL, no hard default since migration is opt-in per repo),migrate_source_host(verifies the new URL resolves via HTTP HEAD before writing — refuses to point a repo at a host it hasn't actually migrated to)- CLI:
reuse-surface federation migrate-host --repo <slug> [--repo ...] --to <base-url> [--from <sanity-check>] [--dry-run] [--no-verify] [--update-hub]— used for real onactivity-core/state-hub - Fixed two host-agnostic code gaps found while inventorying:
registry_update.py'sSAFE_EVIDENCE_PREFIXESandmaintain_llm.py's git-diff pathspec only recognized.gitea/workflows/, missing repos already on.forgejo/workflows/— both now recognize either - Fixed stale copy-paste examples in
docs/RegistryFederation.md(state-hub's old Gitea URL, now genuinely wrong post-migration) and a pre-existing, unrelated port typo (8088vs the real llm-connect default8080) intools/README.md/registry/README.md, discovered and confirmed live during REUSE-WP-0018-T03 - 17 new pytest cases (
tests/test_forge_host.py); 106 total pass - Recomposed
federated.yamlpost-migration: still 61 capabilities, no loss
Hub Recompose Endpoint And Webhook Receiver
id: REUSE-WP-0019-T02
status: done
priority: high
state_hub_task_id: "691eb32a-6f20-4a2a-b9ff-0ae427b659aa"
Note: POST /v1/federated/compose (token-auth, triggers a real
recompose) already existed from earlier hub work — no separate
/v1/recompose route was added; the spec now documents this explicitly
rather than duplicating a route that already does the job.
Implemented in reuse_surface/hub/:
store.py:compose_statetable (composed_at,stale), withrecord_compose()/mark_stale()/get_compose_state().composed_atupdates andstaleclears only on a forced recompose (refresh=true, webhook, or future scheduled fallback) — a plainGETstill serves current best-effort data but never silently reports itself as freshly composedwebhooks.py:verify_signature(constant-time HMAC-SHA256, fails closed on empty secret),push_touches_registry_index(path-only inspection of the push payload'sadded/modified/removedlists — never parses file content, per design principle 2)app.py:POST /v1/webhooks/forgejo(accepts bothX-Forgejo-SignatureandX-Gitea-Signature, since repos migrate independently);GET /v1/federatedandPOST /v1/federated/composenow share anasyncio.Lockso concurrent recompose triggers (manual, webhook, future scheduled) coalesce instead of overlappingspecs/FederationHubAPI.mdextended (§5.7-5.9, config table, error codes)- 28 new pytest cases (16 in
test_hub.py, 12 intest_webhooks.py); 128 total pass - Live-verified: ran the actual hub service locally
(
reuse-surface serve), sent a real HMAC-signed webhook payload over HTTP — confirmedcomposed_at/staletransitions, signature rejection, irrelevant-path no-op, and the full webhook-to-recompose path end to end
Deployed 2026-07-07 (explicit user sign-off "Deploy to live please"):
built and pushed gitea.coulomb.social/coulomb/reuse-surface:e3ae22e,
smoke-tested it locally in a standalone container first, then
helm upgrade via railiance-apps (make reuse-deploy, pinned in
helm/reuse-surface-values.yaml, commits a2c0da1/bcb05f5). Live-verified
against https://reuse.coulomb.social: /v1/federated (200, 61
capabilities, composed_at/stale fields present), /v1/repos (200), a
real forced recompose via POST /v1/federated/compose (token-auth via
warden access --exec, token never printed), and the webhook endpoint
correctly failing closed with 503 (REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
not yet configured).
Webhook secret added 2026-07-07 (explicit user sign-off "Go ahead and
add the webhook secret"): generated a fresh 32-byte hex secret with
openssl rand -hex 32, patched it into the live K8s Secret
reuse-surface-env in namespace reuse, restarted the deployment (values
injected via envFrom.secretRef, not picked up without a restart) — pod
rolled cleanly (1/1 Ready, 0 restarts). The raw secret value was never
printed to the transcript: generated and applied in one non-echoing shell
step, and re-fetched only inside an exported env var for the live
signature-verification test below, then unset. Live-verified against
production: a correctly HMAC-signed push payload is now accepted
({"accepted": false, "reason": "no registry/indexes/ change"} for a
payload that doesn't touch registry/indexes/, as designed), and a bad
signature is still rejected with 401. /v1/federated and /v1/repos
unaffected by the restart.
Found (not fixed) while smoke-testing: the public ingress's exact-path
/health rule 404s at the Traefik edge (shadowed by the catch-all /
rule to the landing page) — confirmed ingress-layer only, not a pod/service
problem (/health works via direct port-forward; the Deployment's own
readiness/liveness probes pass, pod is 1/1 Ready). /v1/* unaffected.
Flagged to railiance-apps via State Hub message and documented in
railiance-apps commit bcb05f5 — not fixed here since it's a shared
production ingress template edit outside this workplan's scope.
Forgejo Webhook Rollout And Scheduled Fallback
id: REUSE-WP-0019-T03
status: done
priority: medium
state_hub_task_id: "aa9e9f80-b878-490c-832e-515d8cbbbb60"
Org-level Forgejo webhook — done by railiance-apps (2026-07-07),
credited not claimed: after I flagged the /health ingress bug to
railiance-apps via State Hub message, they picked up this piece too:
tools/reuse-forgejo-webhook.sh + make reuse-forgejo-webhook
(idempotent, reads the same REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET I put
in the K8s Secret directly from the live cluster, never prints it).
Verified live: GET /api/v1/orgs/coulomb/hooks shows hook id 1, active,
push events only, pointed at https://reuse.coulomb.social/v1/webhooks/forgejo.
Migrated this repo's own CI, following the existing tier-3 playbook
(the-custodian/docs/forgejo-repo-migration-pilot-glas-harness.md,
railiance-enablement/docs/forgejo-actions-workflow-templates.md — used
proven patterns rather than improvising):
- Created
coulomb/reuse-surfaceon Forgejo (private: false, matching every other migrated repo includingstate-hub/activity-core— confirmed via API before asking, then got explicit user sign-off on the visibility choice specifically, since the auto-mode classifier correctly flagged "public" as an inferred parameter beyond the general migrate instruction) .forgejo/workflows/ci.yml: ported.gitea/workflows/ci.ymlverbatim in behavior, but using archive checkout (wget+tar+apt install python3) instead ofactions/checkout@v4— this runner'subuntu-latestlabel maps todocker://node:20-bookwormwith no Python preinstalled and no proven checkout-action support on this substrate.forgejo/workflows/ci-smoke.yaml: routing probe, copied from the canonical template (matches sibling convention).forgejo/workflows/image.yaml: container build/push toforgejo.coulomb.social/coulomb/reuse-surface(canonical single-repo template, org secretsREGISTRY_USER/REGISTRY_TOKENalready existed).forgejo/workflows/recompose-fallback.yaml: scheduled (every 6h)POST /v1/federated/composeas a backstop per design principle 3 ("degrade to schedule") — the webhook above is the primary path; no separate/v1/recomposeroute exists (see T02 note)- Removed
.gitea/workflows/ci.yml(dead onceoriginmoves; matches howstate-hub/activity-core/etc. left no.gitea/workflows/behind) origin→forgejo-remote:coulomb/reuse-surface.git, old remote renamed togitea(kept, read-only, frozen at commitac81490— safety contract from the migration playbook: never delete the Gitea copy)- Live-verified all three workflows green on the actual push:
ci.yml(validate-registry, real pytest+validate suite) success;ci-smoke.yaml(host-smoke+container-smoke) success;image.yaml(build-and-push) success — confirmedreuse-surface:latestandreuse-surface:main-09d5b0fboth landed in the Forgejo container registry via the packages API
REUSE_SURFACE_TOKEN repo secret added 2026-07-07 (explicit user
sign-off "Go ahead and add the repo secret"): fetched the token via
warden access reuse-surface-hub-write-token --exec (never printed) and
PUT it to Forgejo's repo Actions secrets API (201). Live-verified by
manually dispatching recompose-fallback.yaml
(POST .../actions/workflows/recompose-fallback.yaml/dispatches) — run
completed success, and GET /v1/federated on the live hub showed
composed_at advance to the exact dispatch time, confirming the full
scheduled-fallback path works end to end, not just that the workflow
didn't error.
T03 is now fully complete: org webhook live (railiance-apps), this repo's CI on Forgejo Actions (all three workflows verified green), and the scheduled fallback verified working end to end.
Not done — moved out of this workplan's scope: updating
docs/deploy/reuse-kubernetes.md/railiance-apps to make
forgejo.coulomb.social the canonical production deploy image registry
(vs. the current live pod, which still runs the gitea.coulomb.social
image built in T02 and is working fine). Didn't force another production
redeploy in this same pass; the Forgejo image pipeline is proven and ready
whenever a future deploy wants to switch registries.
Reuse Telemetry Store And Recording
id: REUSE-WP-0019-T04
status: done
priority: medium
state_hub_task_id: "c8e9064e-5c39-4c84-80e9-8b255f8edaec"
The shared schema (schemas/reuse-event.schema.json) already existed from
WP-0018-T01 drafting — this task implemented the hub side and wired
plan-check/manual recording against it, rather than designing it fresh.
Implemented:
reuse_surface/hub/store.py:reuse_eventsSQLite table (append-only),record_reuse_event()(schema-validated, raisesValueErroron a bad shape rather than silently accepting drift),list_reuse_events(capability_id=None)reuse_surface/hub/app.py:POST /v1/reuse-events(token-auth, 201/400),GET /v1/reuse-events?capability_id=(no auth, read-only, same posture asGET /v1/federated)reuse_surface/hub_client.py:hub_record_reuse_event/hub_list_reuse_eventsreuse_surface/plan_check.py: refactoredrecord_outcomearound a new sharedpost_or_fallback_reuse_event()— triesPOST /v1/reuse-eventsfirst, falls back to the local JSONL only on failure/unreachability (never both, per the design: "+ local JSONL fallback when hub unreachable", not a dual-write). Newrecord_manual_reuse_event()for retroactive facts, sharing the same post-or-fallback path- New CLI command
reuse-surface record-reuse --consumer-repo --capability-id --verdict --outcome [--hub-url] [--format] plan-check --record-outcome's help text and JSON output updated (recorded_reuse_eventfield);schemas/plan-check-result.schema.jsonextended for the new field- Privacy/scope enforced structurally: the schema's
additionalProperties: falsemeans a caller literally cannot attach code or secrets to an event, only the declared fields (repo slug, capability id, verdict, outcome, source) - 21 new pytest cases (hub store/API, plan_check dual-path, CLI); 145 total pass
- Live-verified against a real locally-running hub instance:
POST/GET /v1/reuse-eventsdirectly,record-reuseCLI posting to the hub,plan-check --record-outcomeposting to the hub, and — after actually killing the hub process — confirmed the fallback path writes correctly to the local JSONL instead of erroring
Telemetry Aggregation Into R-Axis Evidence
id: REUSE-WP-0019-T05
status: wait
priority: medium
state_hub_task_id: "f0282cfa-0a71-4b46-a558-80b51ef04fa7"
Blocked on T04 plus initial event volume.
reuse-surface report reuse: per-capability consumer counts, outcomes, last-used; feedsreused_byrelation suggestions via the WP-0016 maintain/patch pipeline (evidence-gated, never silent promotion)- Maturity standard note: what observed-reuse evidence counts toward R2/R3+
(
specs/CapabilityMaturityStandard.mdamendment) - Catalog + graph surface consumer counts
Freshness Monitoring, Docs, SCOPE
id: REUSE-WP-0019-T06
status: todo
priority: low
state_hub_task_id: "a9f44d45-91e2-4b43-909f-30a5f906cf3b"
reuse-surface stats: hubcomposed_atage + stale flag; CI informational check warns when the hub index is older than N daysdocs/RegistryFederation.md+docs/deploy/reuse-kubernetes.md: webhook setup, recompose endpoint, Forgejo token handling (route credentials per credential-routing rules — no secrets in repo)SCOPE.md: flip "automatic hub refresh" to possible; update federation posture
Acceptance
- No hardcoded forge host in code or sources.yaml;
migrate-hosttested (T01, 2026-07-07) - Push to a sibling repo's
registry/indexes/recomposes the hub index without manual action (webhook), with scheduled fallback in place (T02/T03, 2026-07-07 — both live-verified end to end) - This repo's CI runs on Forgejo Actions (
.forgejo/workflows/) (T03, 2026-07-07 —ci.yml/ci-smoke.yaml/image.yamlall verified green on the live push) - Reuse events recordable via hub API and CLI (T04, 2026-07-08 — live-verified);
report reuseaggregation is T05 - R-axis evidence rules for observed reuse documented in the maturity standard
- Hub freshness visible (
composed_at, stale flag) in API and stats (T02, 2026-07-07)
Out of scope
- Operating the Forgejo instance or the Gitea→Forgejo data migration itself
- Multi-replica/Postgres hub posture (separate managed-platform track)
- Blocking CI gates on registry freshness in sibling repos
- ActivityPub/Forgejo-native federation features (our federation layer stays raw-URL based)