reuse_surface/stats.py: _hub_summary() now reports composed_at, stale,
age_days, freshness_threshold_days (REUSE_SURFACE_FRESHNESS_DAYS env,
default 7), and a computed stale_warning. New hub_client.hub_federated()
backs it. format_stats_markdown surfaces a STALE marker when triggered.
.forgejo/workflows/ci.yml: new informational (non-failing) hub freshness
check against the live production hub on every push -- prints a
:⚠️: annotation when stale, never fails the build.
docs/RegistryFederation.md: new section tying together the webhook (T02),
scheduled fallback (T03), and freshness visibility (T06) into one
explanation. docs/deploy/reuse-kubernetes.md: updated for the T03 Forgejo
migration and the now-automated image.yaml build; image promotion
checklist updated for the known /health ingress bug (verify via
/v1/repos or /v1/federated instead).
14 new pytest cases, 173 total pass. Live-verified against production:
reuse-surface stats correctly showed composed_at/age_days for the real
federated index. Separately discovered and confirmed (via a live signed
webhook test) that reuse-surface-env moving to ExternalSecret/OpenBao
custody (railiance-apps commit 706f6c7, found while updating these docs)
did not break the T02/T03 webhook -- the synced value still matches what
the hub actually uses.
REUSE-WP-0019 is now fully complete (T01-T06). SCOPE.md and
docs/IntentScopeGapAnalysis.md updated to reflect closure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
23 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 | finished | claude-code | helix-forge | 2026-07-06 | 2026-07-08 | 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: done
priority: medium
state_hub_task_id: "f0282cfa-0a71-4b46-a558-80b51ef04fa7"
"Blocked on T04 plus initial event volume" — T04 is done, and the
tooling doesn't actually need to wait for real ecosystem adoption volume
to be built and tested correctly (an empty/near-empty dataset is itself a
valid, tested case: report reuse prints "No reuse events recorded yet"
rather than erroring). Real cross-repo adoption volume ramping up is an
ecosystem-timing question, not a coding blocker — implemented now so the
tooling is ready the moment volume exists.
Implemented:
reuse_surface/reports.py:collect_reuse_events()(merges the hub'sGET /v1/reuse-events, if reachable, with this repo's local JSONL fallback, deduped by(ts, consumer_repo, capability_id, source)),collect_reuse_report()(per-capability consumer counts, outcome breakdown, last-used),format_reuse_report_markdown/jsoncollect_reused_by_suggestions(): evidence-gatedrelation_addsuggestions — only for capabilities this repo actually owns (checked against the local index, not invented), only for consumer repos not already listed. Reuses the existingpatches.py:apply_patchesmechanism (which already handledrelation_add, not inSAFE_DETERMINISTIC_KINDS, so never auto-applied bymaintain --auto) rather than building a new apply path- New CLI:
reuse-surface report reuse [--capability-id] [--format] [--suggest-relations] [--apply]—--applyrequires--suggest-relationsand is the only thing that writes; nothing happens automatically from telemetry alone - Schema: added
relations.reused_bytoschemas/capability.schema.yaml(a newrepoSlugList$defstype — distinct from the existingcapabilityIdListrelations, since reused-by targets are consumer repo slugs, not capability ids) specs/CapabilityMaturityStandard.md§8.9 amended: what observed-reuse evidence counts toward R2→R3 (single corroborating consumer) vs R3→R4+ (multiple independent consumers,report reuse'sconsumer_count), and explicitly what it never substitutes for (bug reports, incidents, explicitpromotion_historycitations —maturity_promotestays LLM-suggested and review-gated)- 19 new pytest cases (
test_reports.py); 162 total pass - Live-verified with synthetic local-JSONL events against a real
capability entry:
report reuse --suggest-relations --applycorrectly wroterelations.reused_byinto the entry's front matter via the realapply_patchespath (confirmed viagit diff, then reverted since it was a smoke test, not a real fact)
Deliberately deferred, not silently dropped: "Catalog + graph surface
consumer counts." graph.py's RELATION_TYPES graph is capability-to-
capability edges; reused_by targets are repo slugs, a different
namespace entirely — forcing it into that edge model would either error or
produce meaningless nodes. catalog.py doesn't currently parse full entry
front matter per capability at all (works off the index). Surfacing
consumer counts in either artifact is a real, separate rendering-layer
task, not a natural extension of what T05 already built — left for a
follow-up rather than rushed in.
Freshness Monitoring, Docs, SCOPE
id: REUSE-WP-0019-T06
status: done
priority: low
state_hub_task_id: "a9f44d45-91e2-4b43-909f-30a5f906cf3b"
SCOPE.md's "automatic hub refresh" flip was already done incidentally in T02/T03 (moved from "not possible yet" to "possible now"); no further change needed there.
Implemented:
reuse_surface/hub_client.py: newhub_federated()(GET /v1/federated)reuse_surface/stats.py:_hub_summary()now also reportscomposed_at,stale,age_days(computed fromcomposed_at),freshness_threshold_days(REUSE_SURFACE_FRESHNESS_DAYSenv, default 7), and a computedstale_warning(age beyond threshold OR the hub's ownstaleflag).format_stats_markdownsurfaces these with a⚠ STALEmarker when triggered;format_stats_jsonpicks them up automatically (no format-specific code needed there).forgejo/workflows/ci.yml: new informational (non-failing) "Hub freshness check" step against the live production hub — prints a::warning::annotation when stale, never fails the builddocs/RegistryFederation.md: new "Automatic recompose and freshness" section tying together the webhook (T02/T03), scheduled fallback (T03), and freshness visibility (T06) into one coherent explanation, pointing at the authoritative operator runbook (railiance-apps) for actual secret/webhook setup rather than duplicating operational steps heredocs/deploy/reuse-kubernetes.md: image section updated to reflect the T03 Forgejo migration (repo canonical remote moved; production image still built from the pre-migration Gitea registry, deliberately not switched over in this task); image promotion checklist updated for the now-automated.forgejo/workflows/image.yamlbuild and the known/healthingress bug (use/v1/repos//v1/federatedfor verification instead)- 14 new pytest cases (
test_stats.py); 173 total pass - Live-verified against the real production hub:
reuse-surface statscorrectly showedcomposed_at/age_daysfor the actual federated index (0.08 days old, no stale warning); separately discovered and confirmed (via a live signed webhook test) that an external secrets-management change —reuse-surface-envis now ExternalSecret-managed from OpenBao (railiance-appscommit706f6c7, found while updating these same docs) — did not break the T02/T03 webhook: the synced value still matches what the hub actually uses
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 done in T05 - R-axis evidence rules for observed reuse documented in the maturity standard (T05, 2026-07-08 —
specs/CapabilityMaturityStandard.md§8.9) - Hub freshness visible (
composed_at, stale flag) in API and stats (T02 API, T06stats/CI, 2026-07-07/08)
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)