Compare commits

..

42 Commits

Author SHA1 Message Date
ac81490b87 REUSE-WP-0019: record live webhook secret addition
Some checks failed
ci / validate-registry (push) Has been cancelled
REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET added to the reuse-surface-env K8s
Secret and the deployment restarted to pick it up, per explicit user
sign-off. Live-verified: a correctly-signed webhook push is now accepted,
a bad signature still 401s, other endpoints unaffected by the restart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:30:55 +02:00
9602b431ba REUSE-WP-0019: record production deploy of T01/T02 work
Some checks failed
ci / validate-registry (push) Has been cancelled
Deployed gitea.coulomb.social/coulomb/reuse-surface:e3ae22e to
reuse.coulomb.social (railiance-apps commits a2c0da1/bcb05f5). Live-
verified all API endpoints. Webhook secret write to the K8s Secret was
blocked by the auto-mode classifier as a distinct credential-establishment
action from the general deploy authorization -- correctly left for
separate explicit sign-off, not worked around. Found and flagged (to
railiance-apps, not fixed here) a pre-existing ingress routing bug on the
exact-path /health rule.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:15:42 +02:00
custodian-sync
e3ae22e35b chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 18:25:50 +02:00
e0a4de3310 REUSE-WP-0019-T02: hub recompose staleness tracking + Forgejo webhook
Some checks failed
ci / validate-registry (push) Has been cancelled
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
custodian-sync
443510276b chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 18:15:47 +02:00
00b7eab154 REUSE-WP-0019-T01: forge host abstraction + URL migration inventory
Some checks failed
ci / validate-registry (push) Has been cancelled
reuse_surface/forge_host.py: parse/derive/rewrite raw index URLs across
Gitea and Forgejo (handles both the legacy /raw/<branch>/... form and the
canonical /raw/branch/<branch>/... form both forges serve without a 303
redirect). 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.

New CLI: reuse-surface federation migrate-host --repo <slug> --to
<base-url> [--from <check>] [--dry-run] [--no-verify] [--update-hub].

Inventory: cross-referenced sources.yaml against each repo's actual git
origin. 11/61 repos already on Forgejo; found 2 with stale sources.yaml
entries (activity-core, state-hub) despite having migrated. Fixed for
real: local sources.yaml + production hub registration (hub update),
verified against GET /v1/federated post-migration. config-atlas's
WP-0017-T06 303 confirmed NOT a host-transition symptom (already diagnosed
there as something else).

Also fixed two host-agnostic gaps found while inventorying:
registry_update.py and maintain_llm.py only recognized .gitea/workflows/,
missing repos already on .forgejo/workflows/. Fixed a stale copy-paste
example (state-hub's now-wrong old URL) in docs/RegistryFederation.md, and
a pre-existing unrelated port typo (8088 vs the real llm-connect default
8080) in tools/README.md and registry/README.md.

17 new pytest cases (tests/test_forge_host.py), 106 total pass.
Recomposed federated.yaml post-migration: still 61 capabilities.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:14:30 +02:00
custodian-sync
5aaa4c31c9 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 17:28:46 +02:00
0c76ccb28b REUSE-WP-0018-T03: LLM semantic rerank for plan-check
Some checks failed
ci / validate-registry (push) Has been cancelled
llm-connect came up locally (mock provider, 127.0.0.1:8080), unblocking
this task.

reuse_surface/plan_check.py: build_rerank_prompt/request_rerank/apply_rerank,
reusing llm_bridge.execute_prompt/extract_json_object (same pattern as
maintain_llm.py's request_maintain_patches). New schema
schemas/plan-check-rerank.schema.json rejects malformed responses (missing
fields, non-JSON, invented candidate ids outside the input set) rather than
guessing.

Per design principle 3 (deterministic matches always rank first),
apply_rerank appends LLM-scored entries after the deterministic list
(kind: 'llm') instead of reordering it -- the trusted base result is
identical with or without the rerank pass. Graceful skip via a 'notes'
field when LLM_CONNECT_URL is unset or the response is malformed, mirroring
maintain.py's no_llm/skip pattern. New --llm-url/--no-llm CLI flags.

Also extended plan-check-result.schema.json for 'notes' and the
(pre-existing, previously unschema'd) 'filed_capability_request' field.

9 new pytest cases; 89 total pass. Live-verified against the running
llm-connect instance: it correctly rejected the mock provider's non-JSON
response and surfaced the skip note, with the deterministic verdict and
match order completely unaffected.

REUSE-WP-0018 is now fully done (T01-T06). Updated
docs/IntentScopeGapAnalysis.md priority 29 to Closed and the workplan's
own frontmatter status to finished.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 17:27:39 +02:00
custodian-sync
a1b3425c84 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 17:05:59 +02:00
af93b2a91c REUSE-WP-0018-T05: ecosystem rollout convention drafted and proposed
Some checks failed
ci / validate-registry (push) Has been cancelled
Gate cleared: WP-0017 finished 2026-07-07, federated.yaml now carries 61
real capabilities (was 24). Dogfooded immediately -- plan-check against
'unified interface for issue tracking across Gitea GitHub GitLab' now
correctly returns extend/capability.infotech.issue-tracking instead of new.

Drafted the query-before-build convention in .claude/rules/workplan-convention.md
(gitignored, local -- propagation happens via the custodian rules-template
mechanism, not a reuse-surface git commit). Sent State Hub message to
custodian-agent proposing it be added to the shared template
(13cecd08-14e8-46d9-880c-65b8fef857cb).

Added reuse_check frontmatter to REUSE-WP-0018 and REUSE-WP-0019 themselves
as the reference implementation, both dogfooded for real against the live
federated index (both correctly verdict 'new').

REUSE-WP-0018 is now done except T03 (LLM semantic rerank), deferred
pending a running llm-connect instance. Updated IntentScopeGapAnalysis.md
priority 29 accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 17:04:55 +02:00
custodian-sync
0a435eaad3 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 16:39:12 +02:00
a5e9a4a242 Finish REUSE-WP-0017: T05 approved, T07 closeout complete.
Some checks failed
ci / validate-registry (push) Has been cancelled
Mark coverage campaign finished with 62/62 roster coverage and 61
production federated capabilities; update SCOPE.md current state and
add completion milestone history.
2026-07-07 16:37:53 +02:00
89ca9b3891 Start REUSE-WP-0017-T05: Forgejo hub URL fixes and compose refresh.
Some checks failed
ci / validate-registry (push) Has been cancelled
Migrate eight stale Gitea hub registrations to Forgejo raw URLs,
recompose production and local federated indexes to 61 capabilities,
and add the T05 human-review checkpoint document.
2026-07-07 14:43:02 +02:00
c97834447d Sync federation state after core-hub registration and hub compose.
Some checks failed
ci / validate-registry (push) Has been cancelled
Pull hub-enabled sources via hub sync (inter-hub excluded), mark
core-hub hub_registered, note inter-hub retirement on the roster, and
refresh federated.yaml to 53 capabilities matching production.
2026-07-07 14:25:21 +02:00
d3a5ec85f1 Add core-hub to roster and federation sources
Some checks failed
ci / validate-registry (push) Has been cancelled
core-hub postdates the WP-0017 61-repo roster snapshot (created 2026-06-27)
but is the confirmed Gen-3 successor to the now-retired inter-hub. Added to
local-repo-roster.yaml (established, capability_status: has) and
sources.yaml using its Forgejo raw URL (forgejo.coulomb.social -- core-hub's
origin remote is already forgejo-remote, ahead of the rest of the roster on
the Gitea->Forgejo transition). publish-check passes.

Recomposed federated.yaml --refresh: 25 -> 54 capabilities (picks up both
core-hub and the WP-0017 drafts pushed to sibling repos in prior sessions,
which the cached compose hadn't fetched yet). capability.infotech.core-hub
now resolvable, so inter-hub's relations.related_to pointer to it is a real
federated reference, not just prose.

hub_registered left false for core-hub -- registration requires
REUSE_SURFACE_TOKEN, which I will not fetch from its Kubernetes Secret or
via any routing-mediated bypass myself (both attempts were correctly
blocked this session). Needs Bernd to run the registration directly or
hand me the token.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:56:18 +02:00
9b02e54bb6 Document inter-hub retirement and maturity reassessment
Some checks failed
ci / validate-registry (push) Has been cancelled
inter-hub is retired (Haskell/IHP build-chain problems), superseded by
core-hub (Python/FastAPI/Postgres). Its capability entry was updated
directly in ~/inter-hub (commit cffdf9e): D3/A1/C1/R0 -> D5/A1/C3/R0,
status: deprecated, relations.related_to -> capability.infotech.core-hub.

core-hub itself is not yet added to reuse-surface's roster/federation
sources -- it postdates the WP-0017 coverage sweep; noted as a follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:45:00 +02:00
custodian-sync
cf3b8f93db chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 01:16:20 +02:00
533b4f0831 REUSE-WP-0017-T06: config-atlas hub registration completed
Some checks failed
ci / validate-registry (push) Has been cancelled
Resolved via the reuse-surface-hub-write-token routing catalog entry
that appeared in ops-warden (points at a Kubernetes Secret on
Railiance01, not OpenBao). config-atlas is now among the 61 sources at
reuse.coulomb.social/v1/federated. hub_registered corrected to true;
61/61 hub-registered, 61/61 publish pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:14:41 +02:00
e1ca9bbbc5 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for reuse-surface
2026-07-07 00:58:27 +02:00
e72966a658 REUSE-WP-0018 T01/T02/T04/T06: plan-check deterministic matching + State Hub bridge
Some checks failed
ci / validate-registry (push) Has been cancelled
T01: specs/PlanCheck.md design doc, plan-check-result.schema.json and
reuse-event.schema.json (the latter shared with WP-0019's reuse telemetry).

T02: reuse_surface/plan_check.py + 'reuse-surface plan-check' CLI command.
Deterministic token-Jaccard matching against registry/indexes/federated.yaml
(reuses overlaps.py's TOKEN_RE rather than a second scoring method), with
reuse/extend/new verdicts, markdown and --format json output, staleness
warning, and --record-outcome JSONL telemetry.

T04: reuse_surface/statehub_bridge.py bridges plan-check 'new' verdicts to
State Hub capability requests (--file-request) and surfaces open requests
with no matching capability (report gaps --check-capability-requests, opt-in
to stay offline-safe). Verified against the live local State Hub API;
status field (not catalog_entry_id presence) is the correct open/closed
signal, and the list endpoint needs a longer timeout than the health check
(~7s observed with 5 rows).

T06: docs (tools/README.md, RegistryFederation.md, SCOPE.md,
IntentScopeGapAnalysis.md priority 29) and an informational CI smoke step.

T03 (LLM rerank) not started -- llm-connect isn't running on this
workstation. T05 (ecosystem rollout) remains blocked: WP-0017 has drafted
entries for all 61 repos but they're still local-only pending its own T05
push/publish pass, so the federated index isn't yet worth rolling out
plan-check as ecosystem convention.

16 new tests, all mocked -- no network calls in the default test run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:57:18 +02:00
2a6818d4fe chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-06:
  - update .custodian-brief.md for reuse-surface
2026-07-06 20:18:12 +02:00
e046562b56 REUSE-WP-0017-T06: diagnose config-atlas publish blocker
Some checks failed
ci / validate-registry (push) Has been cancelled
Two conflated issues: (1) the raw-URL 303 redirect is already followed
correctly by establish.py's urllib-based probe -- re-running
publish-check now passes; the stale roster fail was from the 2026-06-16
sweep, not a live issue. (2) config-atlas was never actually registered
with the production hub (absent from the 60 sources at
reuse.coulomb.social/v1/federated despite hub_registered: true in the
roster) -- corrected to false. Local publish pass is now 61/61; hub
registration itself is blocked on REUSE_SURFACE_TOKEN, which has no
routing catalog entry -- needs Bernd to provide or route it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:16:59 +02:00
8fc975717f REUSE-WP-0017: T05 review summary + fix stale seed_capability_ids
Some checks failed
ci / validate-registry (push) Has been cancelled
Prepared history/2026-07-06-t04-review-summary.md for the T05 human
review pass: full table of all 48 capability entries and 13 no-capability
markers generated from the actual committed files, plus flagged items
(the-custodian confidentiality scoping, vergabe-teilnahme low D1/C0,
markitect-main superseded framing, Forgejo-relevant tooling found along
the way).

Also fixed two pre-existing roster rows with stale empty
seed_capability_ids despite having real published entries: activity-core
and ops-warden. Not new drafts -- a bookkeeping gap predating this workplan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:02:06 +02:00
8726cb8c86 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-06:
  - update .custodian-brief.md for reuse-surface
2026-07-06 19:53:27 +02:00
89b8b34471 REUSE-WP-0017-T04 cohort 3 (final): draft entries for remaining 17 repos
Some checks failed
ci / validate-registry (push) Has been cancelled
open-cmis-tck, open-reuse, ops-bridge, phase-memory, railiance-apps,
railiance-cluster, railiance-enablement, railiance-fabric, railiance-forge,
railiance-infra, railiance-platform, repo-scoping, the-custodian,
user-engine, vantage-point, vergabe-teilnahme, whynot-design.

the-custodian entry deliberately scoped to its non-confidential
runtime/tools/ surface only, given the repo's NDA notice. vergabe-teilnahme
honestly registered at D1/C0 given its own unfilled SCOPE.md.

T04 complete: coverage 44/61 -> 61/61 (48 has-capability, 13 explicit
no-capability, 0 unclassified). All 50 sibling-repo commits across three
cohorts remain local only, pending T05 human review and push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 19:52:19 +02:00
651b84a16b REUSE-WP-0017-T04 cohort 2: draft entries for 10 more has-capability repos
Some checks failed
ci / validate-registry (push) Has been cancelled
issue-core (migrated existing CAPABILITY-issue-tracking.yaml),
kaizen-agentic, key-cape, kontextual-engine, llm-connect, markitect-filter,
markitect-main (registered as superseded legacy platform), markitect-quarkdown,
markitect-tool, net-kingdom. All validate, no overlap with existing
federated capabilities. Committed locally in each sibling repo; push held
for T05 human review. Coverage 34/61 -> 44/61.

Also fixed a stale empty_scaffold_count >= 40 test threshold -- no longer
meaningful as the coverage campaign shrinks that number by design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 19:44:02 +02:00
b48a4edf81 chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-06:
  - update .custodian-brief.md for reuse-surface
2026-07-06 19:06:59 +02:00
8429eea576 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-06:
  - REUSE-WP-0017-T04: progress → wait
2026-07-06 19:06:55 +02:00
6688830bf8 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-06:
  - REUSE-WP-0017-T03: progress → wait
2026-07-06 19:06:55 +02:00
d3ae49fdf3 REUSE-WP-0017-T04 cohort 1: draft entries for 10 has-capability repos
Some checks failed
ci / validate-registry (push) Has been cancelled
artifact-store, can-you-assist, citation-engine, citation-evidence,
email-connect, guide-board, hub-core, info-tech-canon, infospace-bench,
inter-hub. Drafted directly (llm-connect not running locally; this
matches the workplan's no-invented-evidence principle better anyway).
All validate, no overlap with existing 24 federated capabilities.
Committed locally in each sibling repo; push held for T05 human review.
Coverage 24/61 -> 34/61. 27 has-capability repos remain for later cohorts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 19:06:11 +02:00
be1dd56d76 REUSE-WP-0017-T03: mark 13 confirmed no-capability repos, fix repo-seed stale count
Some checks failed
ci / validate-registry (push) Has been cancelled
Roster capability_status now has:11 / none:13 / pending:37. Coverage
24/61. NO_CAPABILITIES.md committed locally in all 13 sibling repos
(push held pending confirmation — touches 13 external default branches).
repo-seed's capability_count was stale (0, should be 1); corrected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:51:54 +02:00
f4a5cefa4b chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-06:
  - update .custodian-brief.md for reuse-surface
2026-07-06 18:42:03 +02:00
e87a07014c chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-07-06:
  - workplan status: ready → active
2026-07-06 18:41:53 +02:00
a6ed45bb22 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-07-06:
  - workplan status: ready → active
2026-07-06 18:41:52 +02:00
f383f01dac REUSE-WP-0017-T02: classification sweep of 51 empty scaffolds
Some checks failed
ci / validate-registry (push) Has been cancelled
37 has-capability / 13 no-capability / 0 missing. Awaiting review before
T03 (no-capability markers) and T04 (draft entries) proceed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:40:58 +02:00
8219a5d253 REUSE-WP-0017-T01: explicit capability_status on roster + coverage split in report gaps
Some checks failed
ci / validate-registry (push) Has been cancelled
Adds capability_status: has|none|pending to every roster row (10 has /
51 pending currently), a coverage_ratio headline, and splits the gap
report's empty-scaffold section into unclassified vs explicitly-none.
Adds templates/NO_CAPABILITIES.template.md for the no-capability marker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:36:05 +02:00
493f23123c Move WP-0017, WP-0018, WP-0019 to ready
Some checks failed
ci / validate-registry (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:31:39 +02:00
64b57e69f0 Write back State Hub workstream/task IDs for WP-0017..0019
Some checks failed
ci / validate-registry (push) Has been cancelled
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 18:18:46 +02:00
365db676cf chore(consistency): sync task status from DB [auto]
Some checks failed
ci / validate-registry (push) Has been cancelled
Updated by fix-consistency on 2026-07-06:
  - update .custodian-brief.md for reuse-surface
2026-07-06 18:18:26 +02:00
e37fc60ecf Add workplans WP-0017..0019: coverage campaign, plan-check loop, Forgejo automation + telemetry
Some checks failed
ci / validate-registry (push) Has been cancelled
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 18:17:32 +02:00
08f3bb5110 feat(federation): register config-atlas and reserve surface.* namespace
Some checks failed
ci / validate-registry (push) Has been cancelled
Add config-atlas as a federation source (capability index) and to the local
repo roster. Document surface.* as a distinct id namespace owned by
config-atlas (typed sibling of capability.*, not federated here) under a new
"Id namespace ownership" section. Raw URL currently 303 (required:false), same
publish block as state-hub/feature-control. Recompose federated.yaml.

Supports config-atlas ATLAS-WP-0002-T05.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:48:20 +02:00
368fb156d4 Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
Some checks failed
ci / validate-registry (push) Has been cancelled
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:28 +02:00
54 changed files with 4831 additions and 396 deletions

View File

@@ -1,18 +1,25 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — reuse-surface
**Domain:** helix_forge
**Last synced:** 2026-06-16 19:38 UTC
**Domain:** infotech
**Last synced:** 2026-07-07 16:25 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
*(none — repo may need first-session setup)*
### Forgejo-native federation automation and reuse telemetry
Progress: 2/6 done | workstream_id: `569be717-34f8-4039-bb26-497685f60159`
**Open tasks:**
- ! Forgejo Webhook Rollout And Scheduled Fallback `aa9e9f80`
- ! Telemetry Aggregation Into R-Axis Evidence `f0282cfa`
- · Reuse Telemetry Store And Recording `c8e9064e`
- · Freshness Monitoring, Docs, SCOPE `a9f44d45`
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("helix_forge")`
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View File

@@ -49,5 +49,8 @@ jobs:
- name: Registry gap report (informational)
run: reuse-surface report gaps || true
- name: Plan-check smoke test (informational)
run: reuse-surface plan-check --intent "smoke test" --format json || true
- name: Run tests
run: pytest -q

162
AGENTS.md
View File

@@ -4,7 +4,7 @@
**Purpose:** Capability registry for planning and implementation reuse based on discovery and delivery maturity.
**Domain:** helix_forge
**Domain:** infotech
**Repo slug:** reuse-surface
**Topic ID:** `f39fa2a3-c491-414c-a91b-b4c5fcc6139c`
**Workplan prefix:** `REUSE-WP-`
@@ -101,159 +101,6 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
---
## Local Developer Workflow
The repository is primarily documentation-first with a small Python CLI for
registry validate, query, and export. There is no long-running service.
### Install
```bash
python3 -m venv .venv
.venv/bin/pip install -e .
```
### Build
No separate build step. Treat Markdown, YAML, and workplan edits as source
artifacts.
### Test / lint
```bash
# Registry validation (schema + index drift)
.venv/bin/reuse-surface validate
# Overlap, catalog, federation, and graph
.venv/bin/reuse-surface overlaps
.venv/bin/reuse-surface catalog
.venv/bin/reuse-surface federation compose
.venv/bin/reuse-surface graph --check
# Federation service (local)
# REUSE_SURFACE_TOKEN=dev-token reuse-surface serve
# Hub CLI (against deployed or local service)
# REUSE_SURFACE_URL=http://127.0.0.1:8000 reuse-surface hub status
# Automated tests
.venv/bin/pytest -q
# Repository hygiene
rg --files
git diff --check
```
When workplan files change, sync ADR-001 file state into State Hub:
```bash
curl -s -X POST "http://127.0.0.1:8000/repos/reuse-surface/sync?fix=true" \
| python3 -m json.tool
```
If the HTTP sync endpoint is unavailable, run the consistency script from the
State Hub checkout:
```bash
cd ~/state-hub
.venv/bin/python scripts/consistency_check.py --repo reuse-surface --fix
.venv/bin/python scripts/consistency_check.py --repo reuse-surface
```
The generated instruction in older workplans says `make fix-consistency
REPO=reuse-surface`; that is still valid when `uv` is installed and on PATH.
On this workstation, the `.venv/bin/python` fallback has been verified.
CI runs `reuse-surface validate` on push and pull requests via
`.gitea/workflows/ci.yml`.
### Run
There is no local service to run from this repository.
### Documentation Review Checklist
- Keep `INTENT.md`, `SCOPE.md`, and `specs/` aligned on the registry-first
reuse boundary.
- Keep maturity definitions in `specs/CapabilityMaturityStandard.md` consistent
with `INTENT.md` and `specs/ProductRequirementsDocument.md`.
- Keep registry entries, indexes, and schemas in `registry/`, `schemas/`, and
`templates/` current when capabilities change.
- Record implementation ideas in workplans, not as premature runtime code in
this repository.
---
## Capability Registry
Before building or documenting a new reusable behavior, query the registry to
avoid duplication and to select the best existing capability for planning or
implementation reuse.
### Orient
```bash
# Fast discovery surface — read federated index when multi-repo
cat registry/indexes/federated.yaml
cat registry/indexes/capabilities.yaml
# CLI discovery and export
.venv/bin/reuse-surface query --discovery-min D4
.venv/bin/reuse-surface export --format yaml
# Authoring template and schema
cat templates/capability-entry.template.md
cat schemas/capability.schema.yaml
# Validation and search guidance
cat registry/README.md
cat tools/README.md
```
### Query workflow
1. Run `.venv/bin/reuse-surface query` with filters, or read the index directly.
2. Filter by `vector`, `tags`, `consumption_modes`, `domain`, or `summary`.
3. Open only matching files under `registry/capabilities/`.
4. Compare candidates using `discovery`, `external_evidence`, `availability`,
and `relations` from the entry front matter.
5. Prefer planning reuse when discovery is strong (`D3+`, especially `D5+`).
6. Prefer implementation reuse only when availability is consumable (`A2+` code,
`A3+` CLI, `A4+` API/SDK).
### Add a new capability
1. Search the index for overlap (UC-RS-015) before creating a new entry.
2. Copy `templates/capability-entry.template.md` to
`registry/capabilities/capability.<domain>.<name>.md`.
3. Start at `D0 / A0 / C0 / R0` when evidence is minimal; keep gaps explicit.
4. Add the entry to `registry/indexes/capabilities.yaml`.
5. Run `.venv/bin/reuse-surface validate`.
### Promote a capability
1. Attach evidence required by `specs/CapabilityMaturityStandard.md` for the
target level.
2. Update `maturity` for discovery/availability and `external_evidence` for
completeness/reliability separately.
3. Refresh the index `vector` and record rationale in the entry body.
4. Do not treat higher availability as proof of reliability or completeness.
### MVP acceptance mapping
| Acceptance criterion | Registry surface |
|---|---|
| Add D0/A0/C0/R0 with minimal friction | template + index + registry README |
| Promote through discovery levels | entry front matter + maturity standard |
| Identify current consumption mode | `availability` + index `consumption_modes` |
| Record expectations and broken expectations | `external_evidence.completeness` |
| Record reliability evidence | `external_evidence.reliability` |
| Search by maturity and availability | `reuse-surface query` or index filters |
| Compare candidates | entry vectors + relations + README guidance |
| Avoid duplicate capabilities | index search before add |
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
@@ -304,6 +151,11 @@ every repo's agent instructions because it is high-frequency, high-risk, and eas
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
@@ -329,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: REUSE-WP-NNNN
type: workplan
title: "..."
domain: helix_forge
domain: infotech
repo: reuse-surface
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex

View File

@@ -1,17 +1,12 @@
{
"agents": {
"coach": {
"path": "agents/agent-coach.md",
"enabled": true
},
"optimization": {
"path": "agents/agent-optimization.md",
"enabled": true
},
"scope-analyst": {
"path": "agents/agent-scope-analyst.md",
"enabled": true
}
}
}
# reuse-surface — Claude Code Instructions
@SCOPE.md
@.claude/rules/repo-identity.md
@.claude/rules/session-protocol.md
@.claude/rules/first-session.md
@.claude/rules/workplan-convention.md
@.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md
@.claude/rules/repo-boundary.md
@.claude/rules/credential-routing.md
@.claude/rules/agents.md

View File

@@ -75,6 +75,11 @@ The MVP registry foundation, CLI tooling (REUSE-WP-0003), federation stack
- **Explore relations interactively** at `docs/graph/index.html`
- **Avoid duplicates** by querying the index and checking overlaps before adding
entries
- **Check before building** with `reuse-surface plan-check` (REUSE-WP-0018) —
match a draft workplan or free-text intent against the federated index and
get a reuse/extend/new verdict; `--file-request` bridges a `new` verdict to
a State Hub capability request; `report gaps --check-capability-requests`
surfaces open requests with no matching capability
Registry **tooling** availability is **A4** (CLI plus hosted hub HTTP API).
Registry **authoring** remains Markdown-first; consumption combines entries, the
@@ -98,21 +103,23 @@ See `tools/README.md` for command reference.
## Current State
- **Status:** active MVP registry with CLI, federation, production hub, and
workstation-wide registry rollout (REUSE-WP-0014 finished).
- **Status:** active registry with CLI, federation, production hub, and
workstation-wide coverage campaign complete (REUSE-WP-0017 finished 2026-07-07).
- **Capabilities (reuse-surface):** 2 helix_forge meta-registry entries in
`registry/capabilities/`.
- **Workstation roster:** 60 local git repos at `~/<slug>/` tracked in
`registry/federation/local-repo-roster.yaml` — all **established**, **60/60**
hub-registered, **60/60** publish-check pass.
- **Federation:** `registry/federation/sources.yaml` **60** sources;
`registry/indexes/federated.yaml`**20** composed capability rows
(0 duplicate-ID warnings; 0 fetch warnings).
- **Workstation roster:** 62 local git repos at `~/<slug>/` tracked in
`registry/federation/local-repo-roster.yaml` — all **established**, **62/62**
hub-registered (inter-hub registration disabled on hub), **62/62**
publish-check pass, **62/62** capability coverage (`has` or explicit `none`).
- **Federation:** `registry/federation/sources.yaml`**61** enabled sources;
`registry/indexes/federated.yaml`**61** composed capability rows
(inter-hub excluded; core-hub included; 0 duplicate-ID warnings at last compose).
- **CLI / service:** `reuse_surface/` — validate, query, export, overlaps,
catalog, federation, graph, hub client, establish/update/stats, `serve`
(FastAPI hub).
- **Production hub:** `https://reuse.coulomb.social`**60** repo registrations;
`GET /v1/federated` serves composed index from published raw URLs.
- **Production hub:** `https://reuse.coulomb.social`**61** enabled repo
registrations; `GET /v1/federated` serves **61** capabilities from published
raw URLs (compose refreshed 2026-07-07).
- **Specs:** `specs/FederationHubAPI.md`, `schemas/hub-registration.schema.yaml`.
- **Docs:** `docs/CapabilityRegistryConcept.md`, `docs/RegistryFederation.md`,
`docs/IntentScopeGapAnalysis.md`, deploy guide `docs/deploy/reuse-kubernetes.md`.
@@ -120,8 +127,9 @@ See `tools/README.md` for command reference.
graph, pytest, informational `report cohorts`, `stats --roster`, `report gaps`.
- **Relation graph:** `docs/graph/capability-graph.mmd`, `docs/graph/index.html`.
- **Searchable catalog:** `docs/catalog/search.html`.
- **Workplans:** REUSE-WP-0001 through REUSE-WP-0014 finished (archived);
**REUSE-WP-0015** finished (federation polish, dedup, planning analytics).
- **Workplans:** REUSE-WP-0001 through REUSE-WP-0015 finished (archived);
**REUSE-WP-0017** capability coverage campaign finished (2026-07-07);
**REUSE-WP-0018** plan-check consumption loop active next.
- **Assessment history:** `history/` — intent/scope assessments, rollout
milestone, dedup plan, per-repo follow-up.
- **Self-assessed vector:** `D5 / A4 / C5 / R3` (see `docs/IntentScopeGapAnalysis.md`).

View File

@@ -202,9 +202,11 @@ See §4 and archived workplans `workplans/archived/`.
| 26 | Federated ID deduplication | Per-owner removal from reuse-surface index | **Closed** (WP-0015-T02) |
| 27 | Planning analytics + standardization | Gap report or standardization tracker | **Partial** — gap report shipped (T03); tracker deferred |
| 28 | Registry maintenance automation | Interactive `maintain` + `--auto` with llm-connect | **Closed** (WP-0016) |
| 29 | Consumption loop (query-before-build) | `plan-check` command + State Hub capability-request bridge | **Closed** — all six tasks (T01T06) shipped 2026-07-07; propagation of the ecosystem convention into the shared rules template is now the-custodian's own follow-through, not a reuse-surface blocker |
**Workplan:** `workplans/REUSE-WP-0016-interactive-registry-maintain.md` (priority 28);
`workplans/REUSE-WP-0015-federation-polish-and-planning-analytics.md` (2527)
`workplans/REUSE-WP-0015-federation-polish-and-planning-analytics.md` (2527);
`workplans/REUSE-WP-0018-plan-check-consumption-loop.md` (priority 29)
**Assessment:** `history/2026-06-16-intent-scope-assessment.md`
**Follow-up docs:**
@@ -236,3 +238,6 @@ See §4 and archived workplans `workplans/archived/`.
| 2026-06-16 | **SCOPE refresh + full INTENT success-criteria mapping**; priorities 2527 proposed |
| 2026-06-16 | Assessment persisted; **REUSE-WP-0015** created for priorities 2527 |
| 2026-06-16 | **REUSE-WP-0016** closed priority 28 (interactive `maintain`, `--auto`, templates) |
| 2026-07-07 | **REUSE-WP-0018** partially closed priority 29 (`plan-check` deterministic matching + State Hub capability-request bridge; LLM rerank and ecosystem rollout remain open) |
| 2026-07-07 | **REUSE-WP-0018-T05** closed following REUSE-WP-0017 completion (61 published capabilities); only T03 (LLM rerank) remains open under priority 29 |
| 2026-07-07 | **REUSE-WP-0018-T03** closed once `llm-connect` came up locally; priority 29 fully closed (all six T01T06 tasks shipped) |

View File

@@ -94,13 +94,24 @@ returns **200** with valid YAML (not a redirect to login or HTML).
Entry bodies remain in the source repo; the index is the federation surface.
### Gitea raw URL shape
### Raw URL shape (Gitea or Forgejo)
The organization is mid-transition from Gitea to Forgejo (REUSE-WP-0019);
sibling repos migrate their git remote independently, so both forms are
valid depending on which host a given repo currently lives on. Prefer the
branch-qualified form — both forges serve it directly with no redirect,
unlike the shorter `/raw/<branch>/...` form which 303-redirects:
```text
https://gitea.coulomb.social/coulomb/<repo>/raw/<branch>/registry/indexes/capabilities.yaml
https://gitea.coulomb.social/coulomb/<repo>/raw/branch/<branch>/registry/indexes/capabilities.yaml
https://forgejo.coulomb.social/coulomb/<repo>/raw/branch/<branch>/registry/indexes/capabilities.yaml
```
Use `main` (or the repo's default branch). Verify before registration:
Use `main` (or the repo's default branch). `reuse-surface federation
migrate-host` rewrites a repo's registered URL from one host to the other
once its remote has actually migrated (verifies reachability before
writing — never blind-rewrites a repo that hasn't moved yet). Verify before
registration:
```bash
curl -fsSI "<raw-url>" | head -n1 # expect HTTP/2 200 or HTTP/1.1 200
@@ -132,17 +143,17 @@ reuse-surface establish --scaffold --domain helix_forge
reuse-surface validate
git push origin main
reuse-surface establish --publish-check \
--raw-url https://gitea.coulomb.social/coulomb/state-hub/raw/main/registry/indexes/capabilities.yaml
--raw-url https://forgejo.coulomb.social/coulomb/state-hub/raw/branch/main/registry/indexes/capabilities.yaml
```
### Ongoing maintenance (from sibling repo)
```bash
export LLM_CONNECT_URL=http://127.0.0.1:8088 # optional
export LLM_CONNECT_URL=http://127.0.0.1:8080 # optional
reuse-surface maintain --all --from-git-since origin/main
reuse-surface maintain --all --auto --no-llm # CI / pre-commit
reuse-surface maintain --publish \
--raw-url https://gitea.coulomb.social/coulomb/state-hub/raw/main/registry/indexes/capabilities.yaml \
--raw-url https://forgejo.coulomb.social/coulomb/state-hub/raw/branch/main/registry/indexes/capabilities.yaml \
--all --auto --no-llm
```
@@ -159,9 +170,9 @@ Copy `templates/Makefile.registry.fragment` for `make registry-maintain` /
5. Optionally `reuse-surface hub sync --merge` to refresh local `sources.yaml`.
**Current blocks (2026-06-16):** `state-hub`, `feature-control`,
`identity-canon`, and `shard-wiki` raw URLs return **303** (not published).
See `history/2026-06-16-hub-registration-blocks.md` for probe evidence and owner
follow-ups.
`identity-canon`, `shard-wiki`, and `config-atlas` raw URLs return **303** (not
published). See `history/2026-06-16-hub-registration-blocks.md` for probe evidence
and owner follow-ups.
## Compose workflow
@@ -191,6 +202,19 @@ remote sources without cache fail compose with a clear error.
`warn` (default): duplicate IDs across sources are kept but reported as
warnings. Consumers must inspect `source_repo` before choosing an entry.
### Id namespace ownership
Federation aggregates `capability.*` ids. A few repos own **distinct id families**
that are *not* capability indexes and therefore never collide with `capability.*`:
| Namespace | Owner repo | Index (not federated here) |
|---|---|---|
| `surface.*` (configuration surfaces) | `config-atlas` | `registry/indexes/surfaces.yaml` |
config-atlas federates only its `capabilities.yaml` (the atlas capability itself);
its `surface.*` registry is a typed sibling. The `surface.*` namespace is reserved
for config-atlas so the two id families stay orthogonal under `collision_policy`.
### Owner migration and deduplication
After REUSE-WP-0014, many capabilities remain in both `reuse-surface` and their
@@ -284,6 +308,23 @@ Register published raw index URLs on the hub, or enable a `url` source in
endpoint requires a token. Agents without sibling repo clones can discover
capabilities from the hub or from HTTP sources plus the local index.
### Query before building (`plan-check`)
Before drafting a new workplan, run `reuse-surface plan-check` (REUSE-WP-0018)
against a draft workplan file or a free-text intent. It matches your intent
against `registry/indexes/federated.yaml` and returns a `reuse | extend | new`
verdict — advisory only, never a gate:
```bash
reuse-surface plan-check workplans/XXX-WP-NNNN-something.md
reuse-surface plan-check --intent "parse invoices and file evidence"
```
On a `new` verdict, `--file-request` bridges the gap to a State Hub
capability request so other domains can see the unmet need; `report gaps
--check-capability-requests` surfaces open requests with no matching
federated capability. See `specs/PlanCheck.md` for the full design.
## Relation graphs
```bash

View File

@@ -0,0 +1,106 @@
# Coverage classification sweep (REUSE-WP-0017-T02)
Classification of the 51 empty-scaffold repos identified in the 2026-07-06
gap report, bucketed into `has-capability` (candidate for a drafted registry
entry) vs `no-capability` (candidate for an explicit
`registry/NO_CAPABILITIES.md` marker). Produced by an Explore-agent survey of
each repo's INTENT/SCOPE/README and top-level layout; rationale is grounded
in what was actually read, not guessed.
**Status: confirmed by Bernd 2026-07-06.** Proceeding to T03/T04 with the two
data-quality adjustments noted below applied.
**Summary:** 37 has-capability, 13 no-capability, 0 missing (all 51
directories exist and are valid git repos).
| Repo | Bucket | Rationale | Candidate capability id |
|---|---|---|---|
| agentic-resources | no-capability | INTENT/SCOPE are aspirational HR-for-agents vision prose; no src/, no package manifest, only docs/registry/workplans | |
| artifact-store | has-capability | Real Python service (67 files, pyproject, migrations, schemas) implementing a generic artifact registry/storage gateway with provenance & retention | `capability.artifact.store` |
| can-you-assist | has-capability | Real `cya` CLI package (45 files, pyproject) — console-native LLM assistant with history/memory | `capability.cli.assistant` |
| citation-engine | has-capability | TS package (45 files) implementing the citation-evidence domain model/API contracts | `capability.citation.engine` |
| citation-evidence | has-capability | TS/Vite app (90 files) — integration shell/reference implementation coordinating the citation-evidence subsystems | `capability.citation.evidence-workspace` |
| citation-work | no-capability | Only INTENT/SCOPE/registry/workplans; no src or code implementing the described review workbench | |
| coordination-engine | no-capability | Ambitious framework prose in INTENT, but only history/spec/registry dirs — no runtime code | |
| domain-tree | no-capability | Only registry/workplans; SCOPE explicitly defers ownership to State Hub, no implementation | |
| email-connect | has-capability | Real Python service (24 files, pyproject, config, spec) — headless email send/track/evidence service with coordination-engine adapter contract | `capability.email.connector` |
| evidence-anchor | no-capability | Only INTENT/SCOPE/registry/workplans; anchoring/highlighting layer described but not implemented | |
| evidence-binder | no-capability | Only INTENT/SCOPE/registry/workplans; binding model described but not implemented | |
| evidence-source | no-capability | Only INTENT/SCOPE/registry/workplans; ingestion/extraction layer described but not implemented | |
| guide-board | has-capability | Real Python package (40 files, pyproject, extensions, profiles) — certification/compliance evidence framework with extension architecture | `capability.compliance.evidence-framework` |
| helix-forge | no-capability | Draft-status (intent_version 0.1.0) capability-ecosystem vision; only design assets, standards docs, and one bootstrap script, no packaged implementation | |
| hub-core | has-capability | Real Python library (81 files, pyproject) providing reusable FastAPI/SQLAlchemy/MCP primitives shared across FOS hub services | `capability.hub.core-library` |
| human-resources | no-capability | Only aspirational HR-for-humans intent prose plus registry/workplans; no implementation | |
| ihp-railiance-probe | no-capability | Self-described in its own INTENT as "a probe — not a product," purely a pipeline-validation canary app | |
| info-tech-canon | has-capability | Real Python service (18 files, pyproject, canon.yaml) implementing a concrete "infospace" service surface (CLI, importable functions, read-only HTTP API) on top of the canon corpus | `capability.canon.info-tech` |
| infospace-bench | has-capability | Real Python workspace/CLI (75 files, pyproject, examples) for creating/evaluating structured "infospaces" | `capability.knowledge.infospace-workspace` |
| inter-hub | has-capability | Haskell/IHP app implementing the Interaction Hub Framework (IHF) — governed interaction substrate with Main.hs, Application, Web, contracts, specs | `capability.hub.interaction-framework` |
| issue-core | has-capability | Real Python package (45 files) already carrying its own `CAPABILITY-issue-tracking.yaml` and `.capability/` dir — issue-tracking service | `capability.issue.tracking` |
| kaizen-agentic | has-capability | Real Python package (57 files, pyproject) — 18-agent library plus agency/memory/coordination framework for deployed project agents | `capability.agent.kaizen-framework` |
| key-cape | has-capability | Real Python service (56 files, spec, config) implementing the lightweight-mode NetKingdom IAM profile (OIDC/PKCE via Authelia/LLDAP/privacyIDEA) | `capability.iam.key-cape` |
| kontextual-engine | has-capability | Large Python package (123 files, pyproject, tpsc.yaml) — context engine with examples/docs | `capability.context.engine` |
| llm-connect | has-capability | Real Python package (62 files, pyproject, contracts) — provider-neutral LLM connector library | `capability.llm.connector` |
| markitect-filter | has-capability | Small but real Python package (10 files, pyproject, examples) — markdown filtering component of the Markitect family | `capability.markitect.filter` |
| markitect-main | has-capability | Large legacy platform (application/domain/services dirs, pyproject, package.json) — markitect umbrella product, now being split into successor repos | `capability.markitect.platform` |
| markitect-quarkdown | has-capability | Small real Python package (adapter.py, pyproject) — Quarkdown integration adapter for Markitect | `capability.markitect.quarkdown-adapter` |
| markitect-tool | has-capability | Large Python package (144 files, pyproject, sbom-tools.yaml) — markdown-native toolkit/CLI, syntax-layer successor to markitect-main | `capability.markitect.tool` |
| net-kingdom | has-capability | Real tooling under tools/ (playbook-capability-contract, security-bootstrap-console, iam-profile-conformance, each with README+py) plus canon schemas — IAM/security tooling suite, despite no top-level src/pyproject | `capability.security.iam-tooling` |
| open-cmis-tck | has-capability | Real Python package (14 files, pyproject, adapters/runners/runtime) — CMIS technology compatibility kit | `capability.cmis.tck` |
| open-reuse | has-capability | Real Python package (cli.py, registry.py, validate.py) — reuse-registry CLI/validator (the tool underlying this very classification workflow) | `capability.reuse.registry-cli` |
| ops-bridge | has-capability | Real Python CLI (36 files, pyproject) — SSH reverse-tunnel lifecycle manager keeping remote nodes connected to the State Hub (matches the "bridge" skill) | `capability.ops.tunnel-bridge` |
| ops-hub | no-capability | Only a single diagnostic bootstrap-API probe script (interhub_gate_probe.py) exists; the described "operational truth surface" (hosts/services/incidents/runbooks) isn't implemented yet | |
| phase-memory | has-capability | Real Python package (59 files, pyproject) — profile-driven memory operating layer producing deterministic dry-run retention/compaction actions | `capability.memory.phase-store` |
| railiance-apps | has-capability | Real helm charts, k8s manifests, and reusable smoke-test/check tooling (tools/*.sh) for deploying/verifying Railiance workloads | `capability.railiance.k8s-deploy-tooling` |
| railiance-cluster | has-capability | Real bin/railiance CLI, lib/ scripts, and ansible playbooks (bootstrap/harden) for standing up Railiance cluster nodes | `capability.railiance.cluster-bootstrap` |
| railiance-enablement | has-capability | Real promote-repo-to-forgejo.sh tool plus reusable CI workflow templates (container-build-push, ci-smoke) | `capability.railiance.ci-enablement` |
| railiance-fabric | has-capability | Real Python package (44 files, pyproject, schemas) — railiance_fabric catalog/export tooling | `capability.railiance.fabric-catalog` |
| railiance-forge | has-capability | Real runner-management tools/scripts (gitea/forgejo runner status/activation) and helm values for the CI runner substrate | `capability.railiance.runner-ops` |
| railiance-infra | has-capability | Real ansible/terraform/scripts (Hetzner provisioning, SSH CA bootstrap, playbooks) plus a capabilities/ playbooks dir — infra-as-code automation | `capability.railiance.infra-automation` |
| railiance-platform | has-capability | Extensive real scripts/ (OpenBao/Vault apply/verify, credential-change Python tooling) — secrets/credential platform automation | `capability.railiance.secrets-platform` |
| repo-scoping | has-capability | Real Python package (88 files, pyproject, migrations) — repo scoping/classification service | `capability.repo.scoping` |
| repo-seed | has-capability | Already publishes a real capability entry (`registry/capabilities/capability.infotech.repo-template.md`) — git repo template with State Hub onboarding scaffold | `capability.infotech.repo-template` |
| tegwick-control | no-capability | Personal life/company planning notes (areas/, agent-tasks/) — a private portfolio-tracking meta-repo, not a reusable component | |
| the-custodian | has-capability | Real Python runtime (runtime/agent.py + pyproject) and tools/ scripts (classification batching, Gitea SSH inventory) — the custodian control-plane agent | `capability.custodian.runtime-agent` |
| user-engine | has-capability | Real Python package (36 files, pyproject, migrations) — user/identity domain service | `capability.identity.user-engine` |
| vantage-point | has-capability | Substantial draft protocol spec (nbgm-spec-v0.1.md) defining the Network-Based Graph Model — a well-defined, versioned data model spec, even without code yet | `capability.graph.nbgm-spec` |
| vergabe-teilnahme | has-capability | Full Django application (413 files, manage.py, vergabe_teilnahme package, vite frontend) for German public-procurement (Vergabe) tender participation | `capability.procurement.vergabe-teilnahme` |
| whynot-control | no-capability | Business-signal/beta-tracking scaffolding (betas/, prototypes/, offers/, signals/) with only README stubs — no independent reusable component | |
| whynot-design | has-capability | Real JS design-system package (9+ src files: elements/atoms, chrome, layout, icons, styles) with adapters/tokens — cross-framework design system | `capability.design.whynot-system` |
## Notes on borderline calls
- **repo-seed** already has a capability entry (`capability.infotech.repo-template`)
in the federated index — it shouldn't have been in the empty-scaffold list at
all; the roster's `capability_count` for it is stale. Flag as a data-quality
fix alongside T03/T04, not a fresh draft.
- **issue-core** already carries its own `CAPABILITY-issue-tracking.yaml` and
`.capability/` directory outside the standard `registry/` layout — likely a
pre-existing capability description in a non-standard location that needs
migrating into `registry/capabilities/`, not a from-scratch draft.
- **vantage-point** has no code yet, only a versioned protocol spec — flagged
`has-capability` on the strength of the spec itself (D-axis can be honest
and non-zero for a documented model; A-axis should stay low/A0 until there's
an implementation).
- **net-kingdom** and **railiance-*** repos generally lack a top-level
`pyproject.toml`/package manifest but do have real, runnable tooling under
`tools/`/`scripts/` — maturity vectors for these should reflect
script-level availability (likely A1A2), not assume packaged distribution.
- 13 no-capability repos cluster into two patterns: **vision-only** (INTENT/SCOPE
prose with no implementation — agentic-resources, citation-work,
coordination-engine, domain-tree, evidence-anchor, evidence-binder,
evidence-source, human-resources, helix-forge, ops-hub) and **explicitly
out-of-scope for reuse** (ihp-railiance-probe is a self-described probe,
tegwick-control and whynot-control are personal/business tracking, not
product components).
## Next steps (blocked on review)
- **T03** (mark no-capability): write `registry/NO_CAPABILITIES.md` into the
13 confirmed no-capability repos, set `capability_status: none` in the
roster.
- **T04** (draft entries): run `establish --discover` for the 37 (35, net of
the two data-quality fixes above) confirmed has-capability repos in cohorts
of ~10, honest low-maturity first pass.
**Awaiting Bernd's confirmation of this bucket split before T03/T04 proceed**,
per the workplan's human-review-checkpoint design principle.

View File

@@ -0,0 +1,171 @@
# REUSE-WP-0017-T04 review summary
> **Update 2026-07-07:** `inter-hub` has been retired (Haskell/IHP
> build-chain problems) and superseded by `core-hub` (Python/FastAPI/Postgres).
> Its capability entry was reassessed and marked `deprecated` — see
> `history/2026-07-07-inter-hub-retirement.md`. The D3/A1/C1/R0 vector in
> the table below is now stale for that one row; current vector is
> D5/A1/C3/R0.
Prepared for the T05 human-review-and-publish-pass checkpoint. Everything
below is generated from the actual files committed in each sibling repo
(not from drafting notes), so it reflects what would actually get pushed
and published.
**Coverage: 61/61.** 48 repos have a capability entry, 13 have an explicit
`registry/NO_CAPABILITIES.md` marker, 0 remain unclassified.
**What's new this session (T02T04):** 30 sibling repos got a first commit —
13 no-capability markers (T03) and 17 newly drafted capability entries
(the first two T04 cohorts + this final cohort — see per-cohort commit
messages in `workplans/REUSE-WP-0017-capability-coverage-campaign.md` for
the exact 10/10/17 split). The other 18 `has` rows below already existed
before this workplan (activity-core, audit-core, config-atlas,
feature-control, flex-auth, identity-canon, ops-warden, repo-seed,
reuse-surface, shard-wiki, state-hub, plus the ones drafted in cohorts
12 — see the table for the full picture; cohort provenance is in the
workplan file, not repeated here).
**Everything below is committed locally only.** Nothing has been pushed to
any sibling repo's remote. T05 is where you review, then push +
publish-check + hub refresh + `federation compose` happen.
## How to review a single entry
```bash
cd ~/<repo-slug>
git log -1 --stat # see exactly what was added
git show HEAD # full diff
cat registry/capabilities/<id>.md # or registry/NO_CAPABILITIES.md
```
To push everything after review (per-repo, once you're satisfied):
```bash
git -C ~/<repo-slug> push origin main
```
## Things worth a closer look before pushing
1. **`the-custodian`** — the repo root carries an NDA/confidentiality
notice. The entry (`capability.agents.custodian-runtime-tooling`) is
deliberately scoped to only the non-confidential `runtime/` agent
framework and `tools/` repo-classification scripts; canon/memory content
is explicitly excluded in `discovery.excludes` and
`consumer_guidance.not_recommended_for`. Please double-check this
scoping is exactly right before pushing — this is the one entry where
getting the boundary wrong has real consequences.
2. **`vergabe-teilnahme`** — its own `SCOPE.md` is an unfilled template, so
the entry is honestly low (D1/A1/C0/R0) with a limitation note that
filling in SCOPE.md should happen before further promotion.
3. **`markitect-main`** — registered as a *superseded legacy platform*,
with `consumer_guidance.not_recommended_for` pointing new consumers at
its three successors (`markitect-tool`, `infospace-bench`,
`kontextual-engine`). Worth confirming that's the framing you want on
the record.
4. **`issue-core`** — migrated its own pre-existing
`CAPABILITY-issue-tracking.yaml` (a rich, non-standard capability
manifest with 109 tests / 61% coverage documented) into the standard
`registry/capabilities/` location, rather than drafting fresh. Worth
confirming nothing was lost in translation.
5. **Stale repo-seed-template READMEs**`inter-hub`, `open-reuse`, and
`vantage-point` all still show the generic `repo-seed` bootstrap README
at their repo root; I used each repo's `SCOPE.md`/`INTENT.md` instead
(noted in each entry's discovery rationale) since those are accurate.
The stale READMEs themselves are a small, separate cleanup — not fixed
here, out of scope for this workplan.
6. **Forgejo-transition tooling surfaced along the way**`railiance-enablement`
(`tools/promote-repo-to-forgejo.sh`), `railiance-apps`
(`tools/forgejo-smoke.sh`), and `the-custodian`
(`tools/patch-forgejo-remote-urls.sh`) all ship tooling directly relevant
to REUSE-WP-0019's host-migration inventory (T01) — worth cross-referencing
when that workplan starts.
7. **Two pre-existing roster rows had a stale `seed_capability_ids: []`**
despite having real, already-published entries: `activity-core`
(`capability.activity.event-coordinate`) and `ops-warden`
(`capability.security.ssh-certificate-issuance`). Fixed as a small
incidental correction in the roster — not new drafts, just a bookkeeping
gap from before this workplan.
8. **`config-atlas`** remains `publish_check: fail` (303 error) — tracked
separately as REUSE-WP-0017-T06, likely related to the Gitea→Forgejo
transition (REUSE-WP-0019-T01).
## All 48 capability entries
| Repo | Capability ID | Vector (D/A/C/R) | Summary |
|---|---|---|---|
| `activity-core` | `capability.activity.event-coordinate` | D3/A1/C1/R0 | Coordinate structured responses to cross-domain events through activity workflows and automation. |
| `artifact-store` | `capability.infotech.artifact-store` | D3/A1/C1/R0 | Generic artifact registry and storage gateway for generated outputs, evidence packages, reports, logs, snapshots, exports, and release artifacts. |
| `audit-core` | `capability.audit.event-retain` | D4/A2/C2/R1 | Collect, normalize, retain, and search audit events with integrity evidence across tenants. |
| `can-you-assist` | `capability.agents.cli-assistant` | D2/A2/C1/R0 | Console-native, backend-agnostic assistant CLI that expresses user intent in natural language and returns safe, explainable, context-aware help. |
| `citation-engine` | `capability.infotech.citation-engine` | D3/A1/C1/R0 | Core domain model and engine services for the citation-evidence ecosystem — shared vocabulary, in-memory repositories, orchestration services, event bus, and citation card renderers. |
| `citation-evidence` | `capability.infotech.citation-evidence-workspace` | D4/A1/C1/R0 | Document-centered evidence workspace for capturing, managing, presenting, and re-opening citations — the umbrella application over the citation-evidence six-package design. |
| `config-atlas` | `capability.infotech.config-surface-atlas` | D5/A0/C2/R2 | Read-first, cross-kind map and evidence layer for configuration surfaces — what configures a system, who owns it, its scope, and where the source of truth lives. |
| `email-connect` | `capability.infotech.email-connector` | D2/A1/C1/R0 | Headless, provider-neutral email communication and evidence service; first slice scans a mailbox or fixture directory and produces timestamped CSV evidence reports. |
| `feature-control` | `capability.feature-control.evaluate` | D5/A4/C3/R3 | Evaluate whether a feature is active, hidden, disabled, or unavailable for a subject in context. |
| `flex-auth` | `capability.authorization.policy-evaluate` | D4/A2/C2/R1 | Evaluate access decisions from policy-as-code rules for subjects, resources, and actions. |
| `guide-board` | `capability.communication.compliance-evidence-framework` | D2/A2/C1/R0 | Framework that turns standards, conformance, regulatory, and repository-quality claims into structured, reviewable, repeatable, comparable evidence via a pluggable extension architecture. |
| `hub-core` | `capability.infotech.hub-core-library` | D2/A1/C1/R1 | Reusable FastAPI, SQLAlchemy, and MCP primitives extracted from the State Hub for use by other FOS (Federation of Services) hubs. |
| `identity-canon` | `capability.identity.subject-resolution` | D3/A0/C1/R0 | Resolve who or what is acting in a context by mapping principals, accounts, actors, and identifiers to a stable subject model. |
| `info-tech-canon` | `capability.infotech.canon-service` | D2/A2/C1/R0 | Concrete service surface (CLI, importable functions, read-only local HTTP API) over the InfoTechCanon information-processing infospace, backed by infospace-bench. |
| `infospace-bench` | `capability.communication.infospace-workspace` | D3/A1/C2/R1 | Workspace and service for creating, developing, evaluating, and inspecting structured knowledge spaces (infospaces); application-layer successor to the infospace work begun in markitect-main. |
| `inter-hub` | `capability.infotech.interaction-hub-framework` | D3/A1/C1/R0 | Specification and reference implementation of a governed, observable interaction substrate connecting rendered UI widgets to structured feedback, requirements, decisions, implementation changes, and observed outcomes. |
| `issue-core` | `capability.infotech.issue-tracking` | D4/A2/C2/R1 | Unified Python/CLI interface for issue tracking across Gitea, GitHub, and GitLab, preventing direct platform API usage and credential sprawl for coordinating agents. |
| `kaizen-agentic` | `capability.agents.kaizen-framework` | D3/A2/C1/R0 | AI agency framework providing 18 specialized deployable agent instruction sets plus persistent, project-scoped memory and cross-agent coordination via a Coach meta-agent. |
| `key-cape` | `capability.iam.key-cape` | D4/A2/C2/R1 | Lightweight-mode implementation of the NetKingdom IAM Profile (versioned OIDC/PKCE contract), orchestrating Authelia, LLDAP, and privacyIDEA so applications integrate against the profile, not against implementation internals. |
| `kontextual-engine` | `capability.communication.context-engine` | D4/A1/C1/R0 | Headless knowledge-operations engine turning heterogeneous information assets into persistent, contextual, governed, retrievable, transformable, and agent-operable knowledge. |
| `llm-connect` | `capability.agents.llm-connector` | D3/A2/C2/R1 | Provider-neutral Python/CLI LLM adapter library supporting OpenRouter, Gemini, OpenAI, and the Claude Code CLI out of the box, with a clean abstract interface for adding new providers. |
| `markitect-filter` | `capability.communication.markitect-source-adapters` | D2/A1/C1/R0 | Concrete source-format adapters (EPUB3, PDF) converting external document formats into canonical Markitect Markdown, implementing the markitect-tool source adapter contract. |
| `markitect-main` | `capability.communication.markitect-legacy-platform` | D3/A1/C1/R0 | Intelligent markdown engine and information-management platform treating documents as structured, queryable information spaces with schema validation, transclusion, and LLM-driven evaluation; the legacy umbrella now being split into markitect-tool, infospace-bench, and kontextual-engine. |
| `markitect-quarkdown` | `capability.communication.markitect-quarkdown-adapter` | D3/A1/C1/R0 | Concrete Quarkdown render/export adapter for Markitect, mapping Markitect profiles to Quarkdown profiles and running controlled Quarkdown CLI execution plans without forking or reimplementing Quarkdown. |
| `markitect-tool` | `capability.communication.markitect-toolkit` | D4/A2/C2/R1 | Markdown-native toolkit and CLI (mkt) for turning semi-structured Markdown into structured, queryable, reusable knowledge artifacts; syntax-layer successor to markitect-main. |
| `net-kingdom` | `capability.security.iam-tooling-suite` | D3/A2/C1/R1 | Dynamic, self-optimizing security platform for Kubernetes-deployed IT infrastructure; owns canonical IAM/security standards and executable conformance tooling (IAM profile conformance, playbook capability contract validation, security bootstrap console). |
| `open-cmis-tck` | `capability.cmis.tck` | D2/A1/C1/R0 | CMIS conformance-preparation extension for guide-board, keeping CMIS-specific runner code, profiles, capability mappings, and workplans outside the generic compliance framework. |
| `open-reuse` | `capability.infotech.oss-integration-continuity` | D2/A1/C1/R0 | Turns proven open-source integrations into structured, maintainable, continuously managed assets with clear boundaries and update loops, so they remain robust and transparent as upstream evolves. |
| `ops-bridge` | `capability.ops.tunnel-bridge` | D3/A2/C2/R1 | CLI that manages named SSH reverse tunnels keeping remote execution environments connected to the local Custodian State Hub, with auto-reconnect, health checks, and structured audit events. |
| `ops-warden` | `capability.security.ssh-certificate-issuance` | D4/A3/C3/R2 | Issue short-lived CA-signed SSH certificates for adm, agt, and atm actors through a stable cert_command CLI interface; steward operational access routing across NetKingdom security lanes. |
| `phase-memory` | `capability.memory.phase-planning` | D3/A1/C1/R0 | Interprets Markitect memory profiles as runtime plans, modeling memory phases and producing deterministic dry-run actions for retention, refresh, compaction, stabilization, and activation. |
| `railiance-apps` | `capability.railiance.workload-deployment-tooling` | D3/A2/C1/R1 | S5 Workloads and Experience Endpoints layer of the Railiance OAS Stack — application Helm releases, Kubernetes workload manifests, deployment guardrails, and smoke-test/check tooling for user-facing services. |
| `railiance-cluster` | `capability.railiance.cluster-bootstrap` | D3/A2/C1/R0 | Cluster runtime entry point of the Railiance Infrastructure-as-Code framework: from two bare Linux servers, a Git repo, and credentials, rebuilds a fully automated Kubernetes-based environment. |
| `railiance-enablement` | `capability.railiance.ci-enablement` | D3/A2/C2/R1 | S4 Developer Enablement layer of the Railiance OAS Stack — reusable CI/CD workflow templates, developer portal paths, platform templates, SDKs, and buildpacks, using forge capabilities without owning forge runtime. |
| `railiance-fabric` | `capability.railiance.fabric-graph` | D3/A1/C1/R0 | Models the durable infrastructure-responsibility graph of the Railiance netkingdom: schemas, discovery tools, registry services, graph queries, and State Hub export contracts for services, machines, repos, deployables, endpoints, ownership, dependencies, and bindings. |
| `railiance-forge` | `capability.railiance.forge-infrastructure` | D3/A1/C1/R0 | Source forge, registry, and automation-runner infrastructure for Railiance, separated out from railiance-apps/railiance-enablement; covers current Gitea operation, the Forgejo migration, container/package registries, and Actions runner substrate. |
| `railiance-infra` | `capability.railiance.infra-provisioning` | D4/A2/C2/R1 | Git-driven server provisioning and convergence for Hosteurope/Hetzner Cloud using Terraform, cloud-init, and Ansible, with SOPS+age encrypted in-repo secrets and a servers.yaml inventory as source of truth. |
| `railiance-platform` | `capability.railiance.platform-services` | D3/A2/C1/R0 | S3 Platform Services layer of the Railiance OAS Stack — shared cluster services: PostgreSQL HA, Valkey cache, secret management, identity integration, and object storage. |
| `repo-scoping` | `capability.agents.repo-scoping-service` | D2/A2/C1/R0 | Maps repositories from usefulness to implementation (Ability -> Capability -> Feature -> Evidence -> Code location) via a Python registry core, FastAPI HTTP API, and curator UI. |
| `repo-seed` | `capability.infotech.repo-template` | D3/A3/C2/R2 | Bootstrap new git repositories with agent instructions, registry scaffold, and State Hub onboarding conventions. |
| `reuse-surface` | `capability.registry.register` | D3/A4/C2/R3 | Register a new capability so it becomes visible for planning and implementation reuse. |
| `shard-wiki` | `capability.wiki.shard-orchestration` | D5/A2/C2/R1 | Present a union of pages across heterogeneous wiki-shaped shards while preserving each shard's provenance, capabilities, and history. |
| `state-hub` | `capability.statehub.progress-log` | D4/A4/C3/R3 | Record progress events, decisions, and session notes against workstreams and tasks in State Hub. |
| `the-custodian` | `capability.agents.custodian-runtime-tooling` | D2/A1/C1/R0 | Generic tooling from The Custodian's runtime and ecosystem-management surface: an agent runtime framework (context, actions, tool adapters, policies) and repo-classification batch tooling used across the workstation's 61 repos. |
| `user-engine` | `capability.identity.user-engine` | D4/A1/C2/R0 | Headless, multi-application, multi-tenant user management engine covering registration, identity/factor models, entitlement claims, hats/realms/services/assets access profiles, and onboarding journeys. |
| `vantage-point` | `capability.graph.nbgm-spec` | D2/A0/C1/R0 | Generic system and versioned protocol specification for exploring dependency structures as network-based graph models (NBGM), unifying entity/relationship inspection and reasoning across arbitrary domains. |
| `vergabe-teilnahme` | `capability.procurement.vergabe-teilnahme` | D1/A1/C0/R0 | Django application (with a Vite/Tailwind frontend) for managing German public-procurement (Vergabe) tender participation — Ausschreibungs- und Teilnahme-Management-System. |
| `whynot-design` | `capability.design.whynot-system` | D3/A2/C2/R1 | Framework-agnostic visual language for whynot prototype/market-signal artefacts: design tokens, drop-in CSS, Lit-based web components usable from React/Django/Vue/plain HTML, and Django template adapters. |
## All 13 explicit no-capability markers
| Repo | Reason |
|---|---|
| `agentic-resources` | INTENT/SCOPE are aspirational HR-for-agents vision prose; no src/, no package manifest — vision-stage only, nothing implemented to reuse. |
| `citation-work` | Only INTENT/SCOPE/registry/workplans describing a review workbench; no source implementing it yet. |
| `coordination-engine` | Ambitious coordination-framework prose in INTENT, but only history/spec/registry dirs exist — no runtime code yet. |
| `domain-tree` | Only registry/workplans; SCOPE explicitly defers ownership of implementation to State Hub. |
| `evidence-anchor` | Only INTENT/SCOPE/registry/workplans; the anchoring/highlighting layer is described but not implemented. |
| `evidence-binder` | Only INTENT/SCOPE/registry/workplans; the binding model is described but not implemented. |
| `evidence-source` | Only INTENT/SCOPE/registry/workplans; the ingestion/extraction layer is described but not implemented. |
| `helix-forge` | Draft-status (intent_version 0.1.0) capability-ecosystem vision; only design assets and standards docs exist, no packaged implementation. |
| `human-resources` | Aspirational HR-for-humans intent prose plus registry/workplans; no implementation. |
| `ihp-railiance-probe` | Self-described in its own INTENT as a probe, not a product — a pipeline-validation canary app, not intended for reuse. |
| `ops-hub` | Only a single diagnostic bootstrap-API probe script exists; the described operational-truth surface (hosts/services/incidents/runbooks) isn't implemented yet. |
| `tegwick-control` | Personal life/company planning notes (areas/, agent-tasks/) — a private portfolio-tracking meta-repo, not a reusable product component. |
| `whynot-control` | Business-signal/beta-tracking scaffolding (betas/, prototypes/, offers/, signals/) with only README stubs — no independent reusable component. |
## Next steps (T05)
1. Review the flagged items above, especially #1 (the-custodian confidentiality scoping).
2. Spot-check a sample of entries against the actual repos if you want deeper confidence beyond this summary.
3. Push the sibling-repo commits (30 repos have new commits this session; see `git log --oneline -1` in each).
4. `establish --publish-check` per repo where the raw URL needs confirming.
5. `reuse-surface federation compose` + catalog + graph regeneration in this repo.
6. Then T06 (config-atlas 303 fix) can proceed independently.

View File

@@ -0,0 +1,65 @@
# inter-hub retirement and maturity reassessment
Follow-up to the REUSE-WP-0017 coverage sweep, requested by Bernd after
reviewing `history/2026-07-06-t04-review-summary.md` and the READMEs
written for `inter-hub`/`open-reuse`/`vantage-point`.
## Decision
`inter-hub` (the Haskell/IHP Interaction Hub Framework implementation) is
**retired** due to persistent build-chain problems. The same framework
ambition is being rebuilt on a more convenient technology stack as
**`core-hub`** (Python/FastAPI/Postgres, contract-first). This is an owner
decision (Bernd), not a reuse-surface judgment call.
`core-hub` already exists as a repo (`~/core-hub`, first commit
2026-06-27) with its own self-authored capability entry
(`capability.infotech.core-hub`, D3/A1/C1/R1) — it was created after the
last workstation roster snapshot (2026-06-16) and REUSE-WP-0017's coverage
sweep, so it isn't yet part of reuse-surface's federation sources or the
61-repo roster. `core-hub/SCOPE.md` explicitly frames itself as the
"Gen 3" successor to `inter-hub` ("Gen 2") and `state-hub` ("Gen 1"), and
already scopes in Inter-Hub `/api/v2` compatibility and a data-migration
plan.
## Maturity reassessment for `capability.infotech.interaction-hub-framework`
The original REUSE-WP-0017-T04 first-pass entry (2026-07-06) rated this
D3/A1/C1/R0 with availability confidence explicitly flagged low ("not
independently verified in this sweep"). A deeper look at `SCOPE.md`'s own
per-phase log, prompted by writing `inter-hub`'s real README, showed
Phases 09 complete: real `Web/Controller/` modules (governance,
requirements, hub capability manifests, federated policy overlays,
deployment records, agent registrations, webhooks), a `Test/` suite, and a
versioned external API (`/api/v2`) with OpenAPI, OAuth client credentials,
generated SDKs, and rate limiting — a substantial working reference
implementation, not a paper spec.
New vector: **D5 / A1 / C3 / R0**, `status: deprecated`.
| Axis | Change | Why |
|---|---|---|
| Discovery | D3 → D5 | Undersold on first pass; Phases 0-9 are genuinely documented and delivered |
| Availability | A1 → A1 (held) | Deliberately **not** raised — the Haskell/IHP build chain is the specific reason for retirement. Feature completeness and build/deploy reliability are different axes; this is exactly the case where they diverge |
| Completeness | C1 → C3 | Nine implementation phases delivered against the original spec's traceability chain |
| Reliability | R0 → R0 (held) | The retirement itself is the reliability signal — a feature-complete implementation that could not be kept dependably buildable. Recorded as an incident, not glossed over |
`relations.related_to` now points at `capability.infotech.core-hub`.
`consumer_guidance` reframed: recommended only for historical
reference/prior-art study; explicitly not recommended for new integration
work.
Committed and pushed directly in `~/inter-hub` (single-repo change, not a
mass push): commit `cffdf9e`.
## Not done (out of scope for this follow-up)
- `core-hub` is not yet added to reuse-surface's `local-repo-roster.yaml`
or `registry/federation/sources.yaml` — it wasn't part of the original
61-repo coverage sweep and adding it means a fresh registration pass
(publish-check, hub registration) of its own. Worth doing as a small
follow-up so the `related_to` relation resolves inside reuse-surface's
own federated index, not just as a prose pointer.
- `core-hub`'s own capability entry (D3/A1/C1/R1) was not touched — it's
self-authored and self-governed by that repo, not part of this
reassessment.

View File

@@ -0,0 +1,67 @@
# REUSE-WP-0017-T05 review checkpoint
Started 2026-07-07. Extends `history/2026-07-06-t04-review-summary.md`.
## Mechanical status (before human sign-off)
| Check | Status |
| --- | --- |
| Roster coverage | 62/62 (`has` or `none`) |
| Publish pass | 62/62 |
| Sibling unpushed registry commits | 0 (spot-check); `railiance-apps` has 1 unrelated unpushed commit |
| Production federated compose | **61 capabilities** / 61 enabled sources (2026-07-07, after Forgejo URL fixes) |
| `core-hub` hub registered | yes (Forgejo URL) |
| `inter-hub` hub disabled | yes |
| Stale Gitea hub URLs (Forgejo origin) | **8 repos fixed** on hub + production compose refreshed |
### Eight repos needing hub URL migration
These publish capability indexes on **Forgejo** but the hub still points at
**Gitea** raw URLs (empty/scaffold indexes):
- `hub-core`
- `issue-core`
- `key-cape`
- `railiance-apps`
- `railiance-cluster`
- `railiance-enablement`
- `railiance-infra`
- `railiance-platform`
Target URL pattern:
`https://forgejo.coulomb.social/coulomb/<slug>/raw/branch/main/registry/indexes/capabilities.yaml`
Broader Gitea→Forgejo migration for the remaining ~53 registrations is
**REUSE-WP-0019-T01**; these eight are the ones blocking federated compose
from picking up T04 drafts today.
## Human review queue (from T04 — still applies)
Review before calling T05 done:
1. **`the-custodian`** — NDA boundary; entry scoped to `runtime/` + `tools/` only.
2. **`vergabe-teilnahme`** — unfilled SCOPE.md; honestly low vector.
3. **`markitect-main`** — superseded-legacy framing.
4. **`issue-core`** — migrated pre-existing manifest; confirm nothing lost.
5. **`inter-hub`** — now `deprecated` / retired; hub disabled 2026-07-07.
6. **`core-hub`** — Gen-3 successor; self-authored entry, not T04 draft.
7. Stale repo-seed READMEs (`open-reuse`, `vantage-point`) — separate cleanup.
## Per-entry review commands
```bash
cd ~/<repo-slug>
git log -1 --stat -- registry/
cat registry/capabilities/*.md # or registry/NO_CAPABILITIES.md
reuse-surface validate --root .
```
## T05 exit criteria
- [x] Human sign-off on flagged entries (Bernd approved 2026-07-07)
- [x] Hub URLs corrected for Forgejo-migrated repos with T04 content
- [x] Production `POST /v1/federated/compose`**61** capabilities
- [x] `hub sync` + local `federation compose --refresh` in reuse-surface
- [x] `reuse-surface catalog` + `graph` regenerated
- [x] Task → `done`; T07 closeout complete (`history/2026-07-07-wp0017-coverage-campaign-complete.md`)

View File

@@ -0,0 +1,35 @@
# REUSE-WP-0017 coverage campaign — complete
**Date:** 2026-07-07
**Workplan:** `workplans/REUSE-WP-0017-capability-coverage-campaign.md`
## Outcome
The workstation capability registry coverage campaign is **finished**.
| Metric | Result |
| --- | --- |
| Roster repos | **62/62** established |
| Capability coverage | **62/62** (`has` or explicit `no-capability`) |
| Unclassified scaffolds | **0** |
| Publish pass | **62/62** |
| Production federated capabilities | **61** (61 enabled hub sources) |
| Local `federated.yaml` | **61** capabilities (matches production) |
## Key events (2026-07-07)
- `core-hub` added to roster and hub-registered (Forgejo URL)
- `inter-hub` retired — capability `deprecated`, hub registration **disabled**
- Eight Forgejo-migrated repos got hub URL fixes (were still on stale Gitea raw URLs)
- Production `POST /v1/federated/compose` refreshed index from 26 → **61** capabilities
- T05 human review approved; follow-up registry commits in `vergabe-teilnahme`,
`markitect-main`, `issue-core`, `core-hub`
## Acceptance
All REUSE-WP-0017 acceptance criteria met (see workplan frontmatter).
## Follow-on
- **REUSE-WP-0018** — plan-check consumption loop (unblocked by published federated index)
- **REUSE-WP-0019** — Gitea→Forgejo host migration for remaining ~53 hub registrations

View File

@@ -70,7 +70,7 @@ reuse-surface maintain --all --auto --no-llm
With llm-connect for maturity suggestions:
```bash
export LLM_CONNECT_URL=http://127.0.0.1:8088
export LLM_CONNECT_URL=http://127.0.0.1:8080
reuse-surface maintain --all --from-git-since HEAD~5
```

View File

@@ -1,25 +1,30 @@
version: 1
updated: '2026-06-16'
updated: '2026-07-07'
workstation_root: /home/worsch
definition: All git repositories one level under the workstation home directory (e.g.
/home/worsch/<slug> with a .git directory). Excludes nested worktrees and non-git
folders.
summary:
total: 60
established: 60
total: 62
established: 62
pending: 0
with_reuse_surface_seed: 8
hub_registered: 60
publish_pass: 60
hub_registered: 62
publish_pass: 62
publish_fail: 0
publish_sweep: '2026-06-16'
publish_sweep: '2026-07-07'
capability_status_has: 49
capability_status_none: 13
capability_status_pending: 0
repos:
- slug: activity-core
path: /home/worsch/activity-core
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.activity.event-coordinate
hub_registered: true
publish_check: pass
batch: B01
@@ -27,6 +32,7 @@ repos:
path: /home/worsch/agentic-resources
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -35,9 +41,11 @@ repos:
- slug: artifact-store
path: /home/worsch/artifact-store
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.artifact-store
hub_registered: true
publish_check: pass
batch: B01
@@ -45,6 +53,7 @@ repos:
path: /home/worsch/audit-core
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.audit.event-retain
@@ -54,27 +63,33 @@ repos:
- slug: can-you-assist
path: /home/worsch/can-you-assist
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.agents.cli-assistant
hub_registered: true
publish_check: pass
batch: B01
- slug: citation-engine
path: /home/worsch/citation-engine
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.citation-engine
hub_registered: true
publish_check: pass
batch: B01
- slug: citation-evidence
path: /home/worsch/citation-evidence
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.citation-evidence-workspace
hub_registered: true
publish_check: pass
batch: B01
@@ -82,24 +97,56 @@ repos:
path: /home/worsch/citation-work
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
publish_check: pass
batch: B01
- slug: config-atlas
path: /home/worsch/config-atlas
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids:
- capability.infotech.config-surface-atlas
hub_registered: true
publish_check: pass
publish_note: local publish-check passes; hub registration completed
2026-07-07 (config-atlas now present among the 61 sources at
https://reuse.coulomb.social/v1/federated). REUSE-WP-0017-T06 closed.
batch: B02
- slug: coordination-engine
path: /home/worsch/coordination-engine
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
publish_check: pass
batch: B01
- slug: core-hub
path: /home/worsch/core-hub
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids:
- capability.infotech.core-hub
hub_registered: true
publish_check: pass
publish_note: added 2026-07-07 -- Gen-3 successor to inter-hub (retired);
not part of the original 61-repo roster snapshot (created 2026-06-27).
Forgejo-native (origin remote is forgejo-remote, not gitea-remote).
Hub registered 2026-07-07; production compose refreshed same day.
batch: null
- slug: domain-tree
path: /home/worsch/domain-tree
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -108,9 +155,11 @@ repos:
- slug: email-connect
path: /home/worsch/email-connect
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.email-connector
hub_registered: true
publish_check: pass
batch: B02
@@ -118,6 +167,7 @@ repos:
path: /home/worsch/evidence-anchor
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -127,6 +177,7 @@ repos:
path: /home/worsch/evidence-binder
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -136,6 +187,7 @@ repos:
path: /home/worsch/evidence-source
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -145,6 +197,7 @@ repos:
path: /home/worsch/feature-control
status: established
capability_count: 3
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.feature-control.evaluate
@@ -157,6 +210,7 @@ repos:
path: /home/worsch/flex-auth
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.authorization.policy-evaluate
@@ -166,9 +220,11 @@ repos:
- slug: guide-board
path: /home/worsch/guide-board
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.compliance-evidence-framework
hub_registered: true
publish_check: pass
batch: B02
@@ -176,6 +232,7 @@ repos:
path: /home/worsch/helix-forge
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -184,9 +241,11 @@ repos:
- slug: hub-core
path: /home/worsch/hub-core
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.hub-core-library
hub_registered: true
publish_check: pass
batch: B02
@@ -194,6 +253,7 @@ repos:
path: /home/worsch/human-resources
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -203,6 +263,7 @@ repos:
path: /home/worsch/identity-canon
status: established
capability_count: 2
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.identity.subject-resolution
@@ -214,6 +275,7 @@ repos:
path: /home/worsch/ihp-railiance-probe
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -222,144 +284,178 @@ repos:
- slug: info-tech-canon
path: /home/worsch/info-tech-canon
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.canon-service
hub_registered: true
publish_check: pass
batch: B03
- slug: infospace-bench
path: /home/worsch/infospace-bench
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.infospace-workspace
hub_registered: true
publish_check: pass
batch: B03
- slug: inter-hub
path: /home/worsch/inter-hub
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.interaction-hub-framework
hub_registered: true
publish_check: pass
publish_note: retired 2026-07-07 (Haskell/IHP build chain); hub registration
disabled (--no-enabled) — excluded from production federated compose.
batch: B03
- slug: issue-core
path: /home/worsch/issue-core
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.issue-tracking
hub_registered: true
publish_check: pass
batch: B03
- slug: kaizen-agentic
path: /home/worsch/kaizen-agentic
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.agents.kaizen-framework
hub_registered: true
publish_check: pass
batch: B03
- slug: key-cape
path: /home/worsch/key-cape
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.iam.key-cape
hub_registered: true
publish_check: pass
batch: B03
- slug: kontextual-engine
path: /home/worsch/kontextual-engine
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.context-engine
hub_registered: true
publish_check: pass
batch: B03
- slug: llm-connect
path: /home/worsch/llm-connect
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.agents.llm-connector
hub_registered: true
publish_check: pass
batch: B03
- slug: markitect-filter
path: /home/worsch/markitect-filter
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.markitect-source-adapters
hub_registered: true
publish_check: pass
batch: B03
- slug: markitect-main
path: /home/worsch/markitect-main
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.markitect-legacy-platform
hub_registered: true
publish_check: pass
batch: B03
- slug: markitect-quarkdown
path: /home/worsch/markitect-quarkdown
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.markitect-quarkdown-adapter
hub_registered: true
publish_check: pass
batch: B04
- slug: markitect-tool
path: /home/worsch/markitect-tool
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.communication.markitect-toolkit
hub_registered: true
publish_check: pass
batch: B04
- slug: net-kingdom
path: /home/worsch/net-kingdom
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.security.iam-tooling-suite
hub_registered: true
publish_check: pass
batch: B04
- slug: open-cmis-tck
path: /home/worsch/open-cmis-tck
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.cmis.tck
hub_registered: true
publish_check: pass
batch: B04
- slug: open-reuse
path: /home/worsch/open-reuse
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.oss-integration-continuity
hub_registered: true
publish_check: pass
batch: B04
- slug: ops-bridge
path: /home/worsch/ops-bridge
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.ops.tunnel-bridge
hub_registered: true
publish_check: pass
batch: B04
@@ -367,6 +463,7 @@ repos:
path: /home/worsch/ops-hub
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -376,53 +473,65 @@ repos:
path: /home/worsch/ops-warden
status: established
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.security.ssh-certificate-issuance
hub_registered: true
publish_check: pass
batch: B04
- slug: phase-memory
path: /home/worsch/phase-memory
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.memory.phase-planning
hub_registered: true
publish_check: pass
batch: B04
- slug: railiance-apps
path: /home/worsch/railiance-apps
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.workload-deployment-tooling
hub_registered: true
publish_check: pass
batch: B04
- slug: railiance-cluster
path: /home/worsch/railiance-cluster
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.cluster-bootstrap
hub_registered: true
publish_check: pass
batch: B05
- slug: railiance-enablement
path: /home/worsch/railiance-enablement
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.ci-enablement
hub_registered: true
publish_check: pass
batch: B05
- slug: railiance-fabric
path: /home/worsch/railiance-fabric
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.fabric-graph
hub_registered: true
publish_check: pass
batch: B05
@@ -430,45 +539,55 @@ repos:
- slug: railiance-forge
path: /home/worsch/railiance-forge
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.forge-infrastructure
hub_registered: true
publish_check: pass
batch: B05
- slug: railiance-infra
path: /home/worsch/railiance-infra
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.infra-provisioning
hub_registered: true
publish_check: pass
batch: B05
- slug: railiance-platform
path: /home/worsch/railiance-platform
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.railiance.platform-services
hub_registered: true
publish_check: pass
batch: B05
- slug: repo-scoping
path: /home/worsch/repo-scoping
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.agents.repo-scoping-service
hub_registered: true
publish_check: pass
batch: B05
- slug: repo-seed
path: /home/worsch/repo-seed
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.infotech.repo-template
hub_registered: true
publish_check: pass
batch: B05
@@ -476,6 +595,7 @@ repos:
path: /home/worsch/reuse-surface
status: established
capability_count: 3
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.registry.register
@@ -487,6 +607,7 @@ repos:
path: /home/worsch/shard-wiki
status: established
capability_count: 8
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.wiki.shard-orchestration
@@ -504,6 +625,7 @@ repos:
path: /home/worsch/state-hub
status: established
capability_count: 2
capability_status: has
seed_from_reuse_surface: true
seed_capability_ids:
- capability.statehub.progress-log
@@ -515,6 +637,7 @@ repos:
path: /home/worsch/tegwick-control
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -523,36 +646,44 @@ repos:
- slug: the-custodian
path: /home/worsch/the-custodian
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.agents.custodian-runtime-tooling
hub_registered: true
publish_check: pass
batch: B05
- slug: user-engine
path: /home/worsch/user-engine
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.identity.user-engine
hub_registered: true
publish_check: pass
batch: B06
- slug: vantage-point
path: /home/worsch/vantage-point
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.graph.nbgm-spec
hub_registered: true
publish_check: pass
batch: B06
- slug: vergabe-teilnahme
path: /home/worsch/vergabe-teilnahme
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.procurement.vergabe-teilnahme
hub_registered: true
publish_check: pass
batch: B06
@@ -560,6 +691,7 @@ repos:
path: /home/worsch/whynot-control
status: established
capability_count: 0
capability_status: none
seed_from_reuse_surface: false
seed_capability_ids: []
hub_registered: true
@@ -568,9 +700,11 @@ repos:
- slug: whynot-design
path: /home/worsch/whynot-design
status: established
capability_count: 0
capability_count: 1
capability_status: has
seed_from_reuse_surface: false
seed_capability_ids: []
seed_capability_ids:
- capability.design.whynot-system
hub_registered: true
publish_check: pass
batch: B06

View File

@@ -3,7 +3,7 @@ domain: helix_forge
collision_policy: warn
sources:
- repo: activity-core
url: https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -58,6 +58,13 @@ sources:
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: config-atlas
url: https://gitea.coulomb.social/coulomb/config-atlas/raw/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: infotech
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: coordination-engine
url: https://gitea.coulomb.social/coulomb/coordination-engine/raw/main/registry/indexes/capabilities.yaml
enabled: true
@@ -65,6 +72,13 @@ sources:
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: core-hub
url: https://forgejo.coulomb.social/coulomb/core-hub/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: domain-tree
url: https://gitea.coulomb.social/coulomb/domain-tree/raw/main/registry/indexes/capabilities.yaml
enabled: true
@@ -129,7 +143,7 @@ sources:
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: hub-core
url: https://gitea.coulomb.social/coulomb/hub-core/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/hub-core/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -170,15 +184,8 @@ sources:
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: inter-hub
url: https://gitea.coulomb.social/coulomb/inter-hub/raw/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: issue-core
url: https://gitea.coulomb.social/coulomb/issue-core/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/issue-core/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -192,7 +199,7 @@ sources:
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: key-cape
url: https://gitea.coulomb.social/coulomb/key-cape/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/key-cape/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -290,21 +297,21 @@ sources:
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: railiance-apps
url: https://gitea.coulomb.social/coulomb/railiance-apps/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/railiance-apps/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: railiance-cluster
url: https://gitea.coulomb.social/coulomb/railiance-cluster/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/railiance-cluster/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: railiance-enablement
url: https://gitea.coulomb.social/coulomb/railiance-enablement/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/railiance-enablement/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -325,14 +332,14 @@ sources:
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: railiance-infra
url: https://gitea.coulomb.social/coulomb/railiance-infra/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/railiance-infra/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: railiance-platform
url: https://gitea.coulomb.social/coulomb/railiance-platform/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/railiance-platform/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge
@@ -368,7 +375,7 @@ sources:
cache_ttl_seconds: 86400
auth_header: Authorization
- repo: state-hub
url: https://gitea.coulomb.social/coulomb/state-hub/raw/main/registry/indexes/capabilities.yaml
url: https://forgejo.coulomb.social/coulomb/state-hub/raw/branch/main/registry/indexes/capabilities.yaml
enabled: true
required: false
domain: helix_forge

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ from jsonschema import Draft202012Validator
from reuse_surface.catalog import write_catalog
from reuse_surface.federation import write_federated_index
from reuse_surface.forge_host import migrate_source_host
from reuse_surface import hub_client
from reuse_surface.graph import check_relations, render_mermaid, write_graph
from reuse_surface.hub_sync import (
@@ -20,6 +21,16 @@ from reuse_surface.hub_sync import (
write_sources_manifest,
)
from reuse_surface.overlaps import find_overlaps
from reuse_surface.statehub_bridge import list_open_capability_requests
from reuse_surface.plan_check import (
format_plan_check_json,
format_plan_check_markdown,
load_query_from_intent,
load_query_from_workplan,
maybe_file_capability_request,
record_outcome,
run_plan_check,
)
from reuse_surface.reports import (
cohort_filters_from_args,
collect_gap_report,
@@ -254,6 +265,56 @@ def cmd_federation_compose(args: argparse.Namespace) -> int:
return 0
def cmd_federation_migrate_host(args: argparse.Namespace) -> int:
sources_path = Path(args.sources) if args.sources else DEFAULT_SOURCES_PATH
manifest = load_sources_manifest(sources_path)
sources = manifest.get("sources", [])
reports: list[dict[str, Any]] = []
errors: list[str] = []
for repo in args.repo:
entry = next((s for s in sources if s.get("repo") == repo), None)
if entry is None:
errors.append(f"{repo}: not found in {sources_path}")
continue
if args.from_host and args.from_host not in entry["url"]:
errors.append(
f"{repo}: current URL does not contain --from {args.from_host!r}: {entry['url']}"
)
continue
try:
report = migrate_source_host(sources, repo, args.to, verify=not args.no_verify)
reports.append(report)
except ValueError as exc:
errors.append(str(exc))
for r in reports:
verified = "unverified" if r["verified"] is None else ("verified" if r["verified"] else "FAILED")
print(f"{r['repo']}: {r['old_url']} -> {r['new_url']} ({verified})")
for e in errors:
print(f"error: {e}", file=sys.stderr)
if args.dry_run:
print("(dry-run, no files written)")
return 1 if errors else 0
if reports:
write_sources_manifest(manifest, sources_path)
if args.update_hub:
for r in reports:
try:
status, payload = hub_client.hub_update(
r["repo"], {"url": r["new_url"]}, args.base_url
)
if status != 200:
errors.append(f"{r['repo']}: hub update returned {status}: {payload}")
except ValueError as exc:
errors.append(f"{r['repo']}: hub update skipped: {exc}")
return 1 if errors else 0
def cmd_graph(args: argparse.Namespace) -> int:
warnings = check_relations() if args.check else []
content = render_mermaid()
@@ -578,16 +639,87 @@ def cmd_report_cohorts(args: argparse.Namespace) -> int:
return 0
def cmd_plan_check(args: argparse.Namespace) -> int:
if args.workplan and args.intent:
print("error: pass either a workplan file or --intent, not both", file=sys.stderr)
return 1
if args.workplan:
path = Path(args.workplan).resolve()
if not path.exists():
print(f"error: workplan not found: {path}", file=sys.stderr)
return 1
query = load_query_from_workplan(path)
elif args.intent:
query = load_query_from_intent(args.intent)
else:
print("error: provide a workplan file or --intent", file=sys.stderr)
return 1
result = run_plan_check(
query,
reuse_threshold=args.reuse_threshold,
extend_threshold=args.extend_threshold,
use_llm=not args.no_llm,
llm_url=args.llm_url,
)
if args.record_outcome:
record_outcome(result, args.record_outcome, consumer_repo=args.consumer_repo)
filed = None
if args.file_request and result["verdict"] == "new":
filed = maybe_file_capability_request(
result,
requesting_domain=args.requesting_domain,
requesting_agent=args.consumer_repo,
)
if args.format == "json":
if filed is not None:
result["filed_capability_request"] = filed
print(format_plan_check_json(result))
else:
print(format_plan_check_markdown(result), end="")
if args.file_request and result["verdict"] == "new":
if filed:
print(f"\nFiled State Hub capability request: {filed.get('id')}")
else:
print("\nState Hub unreachable — capability request not filed.")
return 0
def cmd_report_gaps(args: argparse.Namespace) -> int:
roster_path = Path(args.roster).resolve() if args.roster else default_roster_path()
if not roster_path.exists():
print(f"error: roster not found: {roster_path}", file=sys.stderr)
return 1
report = collect_gap_report(roster_path)
if args.check_capability_requests:
open_requests = list_open_capability_requests()
report["open_capability_requests"] = (
[
{"id": r["id"], "title": r["title"], "requesting_domain": r.get("requesting_domain_slug")}
for r in open_requests
]
if open_requests is not None
else None
)
if args.format == "json":
print(format_gap_json(report))
else:
print(format_gap_markdown(report), end="")
if args.check_capability_requests:
requests_ = report["open_capability_requests"]
print("\n## Open State Hub capability requests (no matching federated capability)\n")
if requests_ is None:
print("_State Hub unreachable — skipped._\n")
elif not requests_:
print("- none\n")
else:
for r in requests_:
print(f"- `{r['id']}` ({r['requesting_domain']}): {r['title']}")
return 0
@@ -669,6 +801,35 @@ def main(argv: list[str] | None = None) -> int:
)
compose.set_defaults(func=cmd_federation_compose)
migrate_host = federation_sub.add_parser(
"migrate-host",
help="rewrite one or more repos' raw index URL to a new forge host (REUSE-WP-0019-T01)",
)
migrate_host.add_argument(
"--repo", action="append", required=True,
help="repo slug to migrate (repeatable)",
)
migrate_host.add_argument(
"--to", required=True,
help="new base URL, e.g. https://forgejo.coulomb.social (or REUSE_SURFACE_FORGE_BASE_URL)",
)
migrate_host.add_argument(
"--from", dest="from_host",
help="sanity check: refuse to migrate a repo whose current URL doesn't contain this",
)
migrate_host.add_argument("--sources", help="path to sources.yaml (default: registry/federation/sources.yaml)")
migrate_host.add_argument("--dry-run", action="store_true", help="show what would change, write nothing")
migrate_host.add_argument(
"--no-verify", action="store_true",
help="skip HTTP-probing the new URL before writing (unsafe -- can point at a repo that hasn't migrated yet)",
)
migrate_host.add_argument(
"--update-hub", action="store_true",
help="also PATCH the production hub registration for each migrated repo",
)
migrate_host.add_argument("--base-url", help="hub service base URL (or REUSE_SURFACE_URL), used with --update-hub")
migrate_host.set_defaults(func=cmd_federation_migrate_host)
query = subparsers.add_parser("query", help="query capability index")
query.add_argument("--discovery-min")
query.add_argument("--availability-min")
@@ -697,6 +858,51 @@ def main(argv: list[str] | None = None) -> int:
)
overlaps.set_defaults(func=cmd_overlaps)
plan_check = subparsers.add_parser(
"plan-check",
help="match a draft workplan or intent against the federated capability index",
)
plan_check.add_argument(
"workplan", nargs="?", help="path to a workplan file (frontmatter + body)"
)
plan_check.add_argument("--intent", help="free-text intent instead of a workplan file")
plan_check.add_argument(
"--format", choices=["markdown", "json"], default="markdown"
)
plan_check.add_argument(
"--reuse-threshold", type=float, default=0.45,
help="score at or above which the verdict is 'reuse'",
)
plan_check.add_argument(
"--extend-threshold", type=float, default=0.22,
help="score at or above which the verdict is 'extend'",
)
plan_check.add_argument(
"--record-outcome",
choices=["reused", "extended", "new", "skipped"],
help="append this outcome to registry/telemetry/plan-check-events.jsonl",
)
plan_check.add_argument(
"--consumer-repo", default="reuse-surface",
help="repo slug recorded with --record-outcome and --file-request",
)
plan_check.add_argument(
"--file-request", action="store_true",
help="on a 'new' verdict, file a State Hub capability request for the gap",
)
plan_check.add_argument(
"--requesting-domain", default="infotech",
help="domain slug recorded on a filed capability request",
)
plan_check.add_argument(
"--llm-url", help="llm-connect base URL (or LLM_CONNECT_URL) for semantic rerank",
)
plan_check.add_argument(
"--no-llm", action="store_true",
help="skip the optional LLM semantic rerank pass",
)
plan_check.set_defaults(func=cmd_plan_check)
catalog = subparsers.add_parser(
"catalog", help="generate human-readable capability catalog"
)
@@ -808,6 +1014,12 @@ def main(argv: list[str] | None = None) -> int:
choices=["markdown", "json"],
default="markdown",
)
gaps.add_argument(
"--check-capability-requests",
action="store_true",
help="also list open State Hub capability requests with no matching "
"federated capability (requires State Hub reachable at 127.0.0.1:8000)",
)
gaps.set_defaults(func=cmd_report_gaps)
stats = subparsers.add_parser("stats", help="registry maturity and federation stats")

105
reuse_surface/forge_host.py Normal file
View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import os
import re
import urllib.error
import urllib.request
from dataclasses import dataclass
from typing import Any
# Matches both the legacy Gitea-default form (.../raw/<branch>/<path>) and the
# canonical branch-qualified form (.../raw/branch/<branch>/<path>) that both
# Gitea and Forgejo serve without a 303 redirect. See REUSE-WP-0017-T06 for
# why the redirect matters (urllib follows it fine; some HEAD-only probes
# and shell tooling don't).
_RAW_URL_RE = re.compile(
r"^(?P<scheme>https?)://(?P<host>[^/]+)/(?P<org>[^/]+)/(?P<repo>[^/]+)"
r"/raw/(?:branch/)?(?P<branch>[^/]+)/(?P<path>.+)$"
)
@dataclass
class RawUrlParts:
scheme: str
host: str
org: str
repo: str
branch: str
path: str
def parse_raw_url(url: str) -> RawUrlParts:
match = _RAW_URL_RE.match(url)
if not match:
raise ValueError(f"not a recognized Gitea/Forgejo raw URL: {url}")
return RawUrlParts(**match.groupdict())
def derive_raw_url(base_url: str, org: str, repo: str, *, branch: str = "main", path: str = "registry/indexes/capabilities.yaml") -> str:
"""Builds a raw index URL in the canonical branch-qualified form, which
both Gitea and Forgejo serve directly (no 303 redirect)."""
base = base_url.rstrip("/")
return f"{base}/{org}/{repo}/raw/branch/{branch}/{path}"
def rewrite_url_host(url: str, new_base_url: str) -> str:
"""Rewrites a raw index URL's scheme+host to new_base_url, preserving
org/repo/branch/path and normalizing to the canonical branch-qualified
form regardless of which form the input used."""
parts = parse_raw_url(url)
return derive_raw_url(new_base_url, parts.org, parts.repo, branch=parts.branch, path=parts.path)
def forge_base_url(explicit: str | None = None) -> str | None:
"""Returns the configured default Forgejo base URL, or None if unset.
Unlike hub_client.service_base_url, this has no hard requirement --
per-repo migration is opt-in (most repos haven't moved their remote
yet), so an unset value just means 'no default target configured',
not an error."""
return explicit or os.environ.get("REUSE_SURFACE_FORGE_BASE_URL") or None
def probe_url(url: str, *, timeout: int = 10) -> tuple[bool, int | None]:
"""HEAD-probes a URL, following redirects (matches establish.py's
_probe_raw_url behavior). Returns (ok, status)."""
request = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "reuse-surface/0.1"})
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.status == 200, response.status
except urllib.error.HTTPError as exc:
return False, exc.code
except (urllib.error.URLError, TimeoutError, OSError):
return False, None
def migrate_source_host(
sources: list[dict[str, Any]],
repo: str,
new_base_url: str,
*,
verify: bool = True,
) -> dict[str, Any]:
"""Rewrites one repo's entry in a sources.yaml `sources` list to point at
new_base_url. Mutates the matching entry in place and returns a report
dict. Raises ValueError if the repo isn't found, the existing URL can't
be parsed, or (when verify=True) the new URL doesn't resolve -- never
writes a URL that hasn't been confirmed reachable, since rewriting to a
host the repo hasn't actually migrated to would silently break
federation for it."""
entry = next((s for s in sources if s.get("repo") == repo), None)
if entry is None:
raise ValueError(f"repo not found in sources: {repo}")
old_url = entry["url"]
new_url = rewrite_url_host(old_url, new_base_url)
report: dict[str, Any] = {"repo": repo, "old_url": old_url, "new_url": new_url, "verified": None}
if verify:
ok, status = probe_url(new_url)
report["verified"] = ok
report["probe_status"] = status
if not ok:
raise ValueError(
f"{repo}: new URL did not resolve (HTTP {status}); "
f"not writing an unverified URL: {new_url}"
)
entry["url"] = new_url
return report

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import asyncio
import json
import os
from pathlib import Path
from typing import Any
@@ -10,6 +12,11 @@ from fastapi.responses import JSONResponse, Response
from reuse_surface.hub.compose import compose_from_store, DEFAULT_DOMAIN
from reuse_surface.hub.store import HubStore
from reuse_surface.hub.webhooks import (
SIGNATURE_HEADERS,
push_touches_registry_index,
verify_signature,
)
HUB_VERSION = "0.1.0"
@@ -30,6 +37,10 @@ def _store() -> HubStore:
return HubStore(_db_path())
def _webhook_secret() -> str:
return os.environ.get("REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET", "")
def _http_error(status: int, error: str, message: str) -> HTTPException:
return HTTPException(
status_code=status,
@@ -53,6 +64,10 @@ def _require_auth(authorization: str | None = Header(default=None)) -> None:
def create_app() -> FastAPI:
app = FastAPI(title="reuse-surface federation hub", version=HUB_VERSION)
store = _store()
# Serializes concurrent recompose triggers (manual POST, webhook, future
# scheduled fallback) so a burst of pushes coalesces into one compose
# pass instead of overlapping ones (T02 design principle 2 debounce).
compose_lock = asyncio.Lock()
@app.get("/health")
def health() -> dict[str, str]:
@@ -96,20 +111,34 @@ def create_app() -> FastAPI:
raise _http_error(404, "not_found", f"repo not found: {repo}")
return Response(status_code=204)
def _federated_response(
async def _federated_response(
refresh: bool,
accept: str | None,
format_param: str,
) -> Response:
# composed_at/stale track *forced* recomposes (refresh=True: manual
# POST, webhook, scheduled fallback), not every plain GET -- a plain
# GET still serves current best-effort data (compose_from_store's own
# per-source cache_ttl_seconds still applies) but must not silently
# clear a staleness signal nothing actually refreshed.
async with compose_lock:
try:
federated, warnings = compose_from_store(
store, refresh=refresh, cache_dir=_cache_dir(), domain=DEFAULT_DOMAIN
)
except FileNotFoundError as exc:
raise _http_error(502, "compose_error", str(exc)) from exc
if refresh:
store.record_compose()
compose_state = store.get_compose_state()
federated["composed_at"] = compose_state["composed_at"]
federated["stale"] = compose_state["stale"]
use_yaml = format_param == "yaml" or (accept and "yaml" in accept.lower())
headers: dict[str, str] = {}
if compose_state["composed_at"]:
headers["X-Composed-At"] = compose_state["composed_at"]
if warnings:
headers["X-Federation-Warnings"] = "; ".join(warnings)
if use_yaml:
@@ -118,19 +147,58 @@ def create_app() -> FastAPI:
return JSONResponse(content=federated, headers=headers)
@app.get("/v1/federated", response_model=None)
def get_federated(
async def get_federated(
request: Request,
refresh: bool = Query(default=False),
format: str = Query(default="json"),
) -> Response:
return _federated_response(refresh, request.headers.get("accept"), format)
return await _federated_response(refresh, request.headers.get("accept"), format)
@app.post("/v1/federated/compose", response_model=None, dependencies=[Depends(_require_auth)])
def compose_federated(
async def compose_federated(
request: Request,
format: str = Query(default="json"),
) -> Response:
return _federated_response(True, request.headers.get("accept"), format)
return await _federated_response(True, request.headers.get("accept"), format)
@app.post("/v1/webhooks/forgejo", response_model=None)
async def forgejo_webhook(request: Request) -> Response:
body = await request.body()
signature = None
for header_name in SIGNATURE_HEADERS:
signature = request.headers.get(header_name)
if signature:
break
secret = _webhook_secret()
if not secret:
raise _http_error(
503, "misconfigured", "REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET is not configured"
)
if not verify_signature(secret, body, signature):
raise _http_error(401, "unauthorized", "invalid or missing webhook signature")
try:
payload = json.loads(body)
except json.JSONDecodeError as exc:
raise _http_error(400, "validation_error", f"invalid JSON payload: {exc}") from exc
if not push_touches_registry_index(payload):
return JSONResponse(content={"accepted": False, "reason": "no registry/indexes/ change"})
async with compose_lock:
store.mark_stale()
try:
compose_from_store(
store, refresh=True, cache_dir=_cache_dir(), domain=DEFAULT_DOMAIN
)
except FileNotFoundError as exc:
# Recompose failed -- leave stale=1 so the next GET/scheduled
# trigger reports it honestly rather than silently swallowing
# the failure.
raise _http_error(502, "compose_error", str(exc)) from exc
composed_at = store.record_compose()
return JSONResponse(content={"accepted": True, "composed_at": composed_at})
return app

View File

@@ -61,6 +61,23 @@ class HubStore:
)
"""
)
# Single-row table tracking the composed federated index's
# freshness (REUSE-WP-0019-T02). composed_at is set whenever a
# real compose (refresh=True) completes; stale is set by the
# Forgejo webhook receiver when a registry/indexes/ change is
# pushed, and cleared on the next successful compose.
conn.execute(
"""
CREATE TABLE IF NOT EXISTS compose_state (
id INTEGER PRIMARY KEY CHECK (id = 1),
composed_at TEXT,
stale INTEGER NOT NULL DEFAULT 0
)
"""
)
conn.execute(
"INSERT OR IGNORE INTO compose_state (id, composed_at, stale) VALUES (1, NULL, 0)"
)
def list_repos(self) -> list[dict[str, Any]]:
with self._connect() as conn:
@@ -161,3 +178,27 @@ class HubStore:
"SELECT * FROM registrations ORDER BY repo"
).fetchall()
return [_row_to_registration(row) for row in rows]
def record_compose(self) -> str:
"""Marks the federated index freshly composed (stale cleared).
Returns the recorded timestamp."""
now = _utc_now()
with self._connect() as conn:
conn.execute(
"UPDATE compose_state SET composed_at = ?, stale = 0 WHERE id = 1",
(now,),
)
return now
def mark_stale(self) -> None:
with self._connect() as conn:
conn.execute("UPDATE compose_state SET stale = 1 WHERE id = 1")
def get_compose_state(self) -> dict[str, Any]:
with self._connect() as conn:
row = conn.execute(
"SELECT composed_at, stale FROM compose_state WHERE id = 1"
).fetchone()
if row is None:
return {"composed_at": None, "stale": False}
return {"composed_at": row["composed_at"], "stale": bool(row["stale"])}

View File

@@ -0,0 +1,37 @@
from __future__ import annotations
import hashlib
import hmac
from typing import Any
# Forgejo (Gitea-compatible) signs webhook payloads with HMAC-SHA256 over the
# raw request body, sent as a hex digest in X-Forgejo-Signature (Forgejo) or
# X-Gitea-Signature (Gitea) -- both forges use the same scheme during the
# transition, so both header names are accepted.
SIGNATURE_HEADERS = ("X-Forgejo-Signature", "X-Gitea-Signature")
RELEVANT_PATH_PREFIX = "registry/indexes/"
def verify_signature(secret: str, body: bytes, signature: str | None) -> bool:
"""Constant-time HMAC-SHA256 verification. Returns False (never raises)
for a missing/malformed signature or a misconfigured (empty) secret --
callers must treat False as 'reject', not 'skip verification'."""
if not secret or not signature:
return False
expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature.strip())
def push_touches_registry_index(payload: dict[str, Any]) -> bool:
"""True if any commit in a Forgejo/Gitea push-event payload adds,
modifies, or removes a path under registry/indexes/. Only inspects
paths -- never parses file content (design principle 2: no
push-parsing of payloads into registry state, the webhook only
triggers a pull-based recompose)."""
for commit in payload.get("commits") or []:
for key in ("added", "modified", "removed"):
for path in commit.get(key) or []:
if path.startswith(RELEVANT_PATH_PREFIX):
return True
return False

View File

@@ -50,6 +50,7 @@ def _git_diff(repo_root: Path, git_since: str | None) -> str:
"tests/",
"docs/",
".gitea/",
".forgejo/",
"pyproject.toml",
],
capture_output=True,

392
reuse_surface/plan_check.py Normal file
View File

@@ -0,0 +1,392 @@
from __future__ import annotations
import json
import re
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import yaml
from jsonschema import Draft202012Validator
from reuse_surface.federation import FEDERATED_INDEX_PATH
from reuse_surface.llm_bridge import execute_prompt, extract_json_object
from reuse_surface.overlaps import TOKEN_RE
from reuse_surface.registry import ROOT, load_index
from reuse_surface.statehub_bridge import file_capability_request
TELEMETRY_PATH = ROOT / "registry" / "telemetry" / "plan-check-events.jsonl"
RERANK_SCHEMA_PATH = ROOT / "schemas" / "plan-check-rerank.schema.json"
STALE_DAYS = 14
DEFAULT_REUSE_THRESHOLD = 0.45
DEFAULT_EXTEND_THRESHOLD = 0.22
DEFAULT_TIE_WINDOW = 0.05
@dataclass
class MatchQuery:
source: str
text: str
workplan_id: str | None = None
workplan_path: str | None = None
tokens: set[str] = field(default_factory=set)
@dataclass
class Match:
id: str
score: float
kind: str
vector: str | None = None
owner: str | None = None
summary: str | None = None
def _tokens(text: str) -> set[str]:
return set(TOKEN_RE.findall(text.lower()))
def load_query_from_workplan(path: Path) -> MatchQuery:
text = path.read_text(encoding="utf-8")
match = re.match(r"^---\n(.*?)\n---\n?(.*)$", text, re.DOTALL)
if not match:
raise ValueError(f"{path}: missing YAML front matter")
front = yaml.safe_load(match.group(1)) or {}
body = match.group(2)
parts = [str(front.get("title") or front.get("id") or path.stem)]
for heading in ("Core Idea", "Problem statement", "One-liner"):
section = re.search(
rf"^##\s+{re.escape(heading)}\s*\n(.*?)(?:\n##\s|\Z)",
body,
re.DOTALL | re.MULTILINE,
)
if section:
parts.append(section.group(1).strip())
if len(parts) == 1:
intro = body.strip().split("\n\n", 1)[0]
parts.append(intro)
blob = "\n".join(p for p in parts if p)
return MatchQuery(
source="workplan",
text=blob,
workplan_id=front.get("id"),
workplan_path=str(path),
tokens=_tokens(blob),
)
def load_query_from_intent(intent: str) -> MatchQuery:
return MatchQuery(source="intent", text=intent, tokens=_tokens(intent))
def _federated_entry_blob(item: dict[str, Any]) -> str:
parts = [
item.get("name", ""),
item.get("summary", ""),
" ".join(item.get("tags", [])),
]
return " ".join(str(p) for p in parts if p)
def load_federated_capabilities() -> tuple[list[dict[str, Any]], str | None]:
"""Returns (capabilities, updated_date). Falls back to the local index
if the composed federated index doesn't exist yet."""
if FEDERATED_INDEX_PATH.exists():
data = yaml.safe_load(FEDERATED_INDEX_PATH.read_text(encoding="utf-8"))
return data.get("capabilities", []), data.get("updated")
data = load_index()
return data.get("capabilities", []), data.get("updated")
def _staleness_warning(updated: str | None) -> str | None:
if not updated:
return None
try:
updated_date = datetime.strptime(updated, "%Y-%m-%d").replace(
tzinfo=timezone.utc
)
except ValueError:
return None
age_days = (datetime.now(timezone.utc) - updated_date).days
if age_days > STALE_DAYS:
return f"federated index is {age_days} days old (last composed {updated})"
return None
def _vector_rank(vector: str | None) -> tuple[int, int]:
"""Higher discovery/availability levels rank first among near-tied scores."""
if not vector:
return (0, 0)
m = re.match(r"D(\d+)\s*/\s*A(\d+)", vector)
if not m:
return (0, 0)
return (int(m.group(1)), int(m.group(2)))
def match_query(
query: MatchQuery,
capabilities: list[dict[str, Any]],
*,
tie_window: float = DEFAULT_TIE_WINDOW,
) -> list[Match]:
if not query.tokens or not capabilities:
return []
scored: list[Match] = []
for item in capabilities:
blob = _federated_entry_blob(item)
tokens = _tokens(blob)
if not tokens:
continue
score = len(query.tokens & tokens) / len(query.tokens | tokens)
if score <= 0:
continue
scored.append(
Match(
id=item["id"],
score=round(score, 4),
kind="deterministic",
vector=item.get("vector"),
owner=item.get("owner"),
summary=item.get("summary"),
)
)
if not scored:
return []
scored.sort(key=lambda m: m.score, reverse=True)
top_score = scored[0].score
tied = [m for m in scored if top_score - m.score <= tie_window]
rest = [m for m in scored if top_score - m.score > tie_window]
tied.sort(key=lambda m: (_vector_rank(m.vector), m.score), reverse=True)
return tied + rest
def load_rerank_schema() -> dict[str, Any]:
return json.loads(RERANK_SCHEMA_PATH.read_text(encoding="utf-8"))
def build_rerank_prompt(query: MatchQuery, matches: list[Match]) -> str:
candidates = [
{"id": m.id, "score": m.score, "vector": m.vector, "summary": m.summary}
for m in matches
]
return (
"You are reranking capability-registry search candidates for a "
"query-before-build check. Score how well each candidate semantically "
"matches the query intent, on a 0-1 confidence scale. You may ONLY "
"return ids from the candidate list below -- do not invent new ones.\n\n"
f"Query intent:\n{query.text}\n\n"
f"Candidates (JSON):\n{json.dumps(candidates, indent=2)}\n\n"
"Respond with a JSON object: "
'{"candidates": [{"id": "...", "confidence": 0.0-1.0, "rationale": "..."}]}. '
"Include every candidate id from the list, in any order."
)
def request_rerank(
query: MatchQuery,
matches: list[Match],
*,
llm_url: str | None = None,
) -> list[dict[str, Any]]:
prompt = build_rerank_prompt(query, matches)
content = execute_prompt(prompt, base_url=llm_url, config={"temperature": 0.0, "max_tokens": 1500})
payload = extract_json_object(content)
validator = Draft202012Validator(load_rerank_schema())
errors = sorted(validator.iter_errors(payload), key=lambda err: list(err.path))
if errors:
messages = "; ".join(error.message for error in errors[:3])
raise ValueError(f"rerank schema validation failed: {messages}")
known_ids = {m.id for m in matches}
candidates = [c for c in payload["candidates"] if c["id"] in known_ids]
if not candidates:
raise ValueError("rerank response contained no known candidate ids")
return candidates
def apply_rerank(matches: list[Match], rerank_result: list[dict[str, Any]]) -> list[Match]:
"""Per spec design principle 3: deterministic matches always rank first.
LLM rerank results are appended after as separately-labeled (kind='llm')
entries carrying the semantic confidence score -- never reordering or
replacing the deterministic base result, so the output is trustworthy
even when a caller ignores the LLM-kind entries entirely (e.g. --no-llm
runs never produce them in the first place)."""
by_id = {m.id: m for m in matches}
llm_matches = [
Match(
id=c["id"],
score=c["confidence"],
kind="llm",
vector=by_id[c["id"]].vector,
owner=by_id[c["id"]].owner,
summary=c.get("rationale") or by_id[c["id"]].summary,
)
for c in rerank_result
if c["id"] in by_id
]
llm_matches.sort(key=lambda m: m.score, reverse=True)
return matches + llm_matches
def verdict_for_score(
top_score: float,
*,
reuse_threshold: float = DEFAULT_REUSE_THRESHOLD,
extend_threshold: float = DEFAULT_EXTEND_THRESHOLD,
) -> str:
if top_score >= reuse_threshold:
return "reuse"
if top_score >= extend_threshold:
return "extend"
return "new"
def run_plan_check(
query: MatchQuery,
*,
reuse_threshold: float = DEFAULT_REUSE_THRESHOLD,
extend_threshold: float = DEFAULT_EXTEND_THRESHOLD,
tie_window: float = DEFAULT_TIE_WINDOW,
top_n: int = 5,
use_llm: bool = True,
llm_url: str | None = None,
) -> dict[str, Any]:
capabilities, updated = load_federated_capabilities()
matches = match_query(query, capabilities, tie_window=tie_window)
top_score = matches[0].score if matches else 0.0
verdict = verdict_for_score(
top_score, reuse_threshold=reuse_threshold, extend_threshold=extend_threshold
)
top_matches = matches[:top_n]
notes: list[str] = []
if use_llm and top_matches:
try:
rerank_result = request_rerank(query, top_matches, llm_url=llm_url)
top_matches = apply_rerank(top_matches, rerank_result)
except ValueError as exc:
if "LLM backend not configured" in str(exc):
notes.append("LLM rerank skipped: LLM_CONNECT_URL not set")
else:
notes.append(f"LLM rerank skipped: {exc}")
result = {
"query": {
"source": query.source,
"text": query.text,
"workplan_id": query.workplan_id,
"workplan_path": query.workplan_path,
},
"verdict": verdict,
"top_score": top_score,
"matches": [
{
"id": m.id,
"score": m.score,
"vector": m.vector,
"owner": m.owner,
"summary": m.summary,
"kind": m.kind,
}
for m in top_matches
],
"federated_index_updated": updated,
"federated_index_stale_warning": _staleness_warning(updated),
}
if notes:
result["notes"] = notes
return result
def format_plan_check_markdown(result: dict[str, Any]) -> str:
lines = [f"# Plan check: {result['verdict']}", ""]
query = result["query"]
label = query.get("workplan_id") or query["text"][:80]
lines.append(f"**Query ({query['source']}):** {label}")
lines.append("")
matches = result.get("matches", [])
if matches:
lines.append("## Matches")
for m in matches:
vec = f" ({m['vector']})" if m.get("vector") else ""
owner = f"{m['owner']}" if m.get("owner") else ""
lines.append(f"- `{m['id']}`{vec}{owner} — score {m['score']:.2f} [{m['kind']}]")
if m.get("summary"):
lines.append(f" > {m['summary']}")
else:
lines.append("_No matches found in the federated index._")
lines.append("")
verdict = result["verdict"]
if verdict == "reuse":
lines.append("**Verdict: REUSE** — an existing capability already covers this; link it instead of building new.")
elif verdict == "extend":
lines.append("**Verdict: EXTEND** — scope overlaps closely enough that extending the top match is likely cheaper than a new capability.")
else:
lines.append("**Verdict: NEW** — no close match in the federated index; proceed.")
warning = result.get("federated_index_stale_warning")
if warning:
lines.append("")
lines.append(f"{warning}")
for note in result.get("notes", []):
lines.append("")
lines.append(f" {note}")
return "\n".join(lines) + "\n"
def format_plan_check_json(result: dict[str, Any]) -> str:
return json.dumps(result, indent=2, sort_keys=True)
def maybe_file_capability_request(
result: dict[str, Any],
*,
requesting_domain: str,
requesting_agent: str,
) -> dict[str, Any] | None:
"""On a 'new' verdict, file a State Hub capability request for the gap.
Returns None (and does nothing) for any other verdict, or if the hub is
unreachable -- this never blocks plan-check's primary output."""
if result["verdict"] != "new":
return None
query = result["query"]
title = (query.get("workplan_id") or query["text"])[:120]
return file_capability_request(
title=f"plan-check gap: {title}",
description=query["text"],
requesting_domain=requesting_domain,
requesting_agent=requesting_agent,
requesting_workplan_id=query.get("workplan_id"),
)
def record_outcome(
result: dict[str, Any],
outcome: str,
*,
consumer_repo: str = "reuse-surface",
) -> Path:
TELEMETRY_PATH.parent.mkdir(parents=True, exist_ok=True)
top_match = result["matches"][0]["id"] if result.get("matches") else None
event = {
"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"consumer_repo": consumer_repo,
"capability_id": top_match,
"verdict": result["verdict"],
"outcome": outcome,
"source": "plan-check",
}
with TELEMETRY_PATH.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(event, sort_keys=True) + "\n")
return TELEMETRY_PATH

View File

@@ -19,7 +19,11 @@ from reuse_surface.registry import (
)
# Safe to apply without interactive review (see patches.SAFE_DETERMINISTIC_KINDS).
SAFE_EVIDENCE_PREFIXES = ("tests/", ".gitea/workflows/")
# Both forge CI workflow paths are recognized during the Gitea->Forgejo
# transition (REUSE-WP-0019-T01) -- sibling repos migrate independently, so
# either path is valid CI evidence depending on a given repo's progress.
CI_WORKFLOW_PREFIXES = (".gitea/workflows/", ".forgejo/workflows/")
SAFE_EVIDENCE_PREFIXES = ("tests/", *CI_WORKFLOW_PREFIXES)
def git_changed_files(repo_root: Path, since_ref: str) -> list[str]:
@@ -194,7 +198,7 @@ def _collect_changed_file_suggestions(
"apply_patch": {"field": "evidence.tests", "append": changed},
}
)
if changed.startswith(".gitea/workflows/") and changed.endswith((".yml", ".yaml")):
if changed.startswith(CI_WORKFLOW_PREFIXES) and changed.endswith((".yml", ".yaml")):
field = "evidence.tests" if "test" in changed.lower() else "evidence.documentation"
existing = evidence_tests if field == "evidence.tests" else evidence_docs
if changed not in existing:

View File

@@ -110,6 +110,9 @@ def collect_gap_report(
r for r in repos
if r.get("status") == "established" and r.get("capability_count", 0) == 0
]
unclassified = [r for r in empty_scaffolds if r.get("capability_status", "pending") == "pending"]
explicit_none = [r for r in empty_scaffolds if r.get("capability_status") == "none"]
covered = [r for r in repos if r.get("capability_status") in ("has", "none")]
seeded = [r for r in repos if r.get("seed_from_reuse_surface")]
dedup_pending = [
{
@@ -133,6 +136,11 @@ def collect_gap_report(
],
"empty_scaffold_count": len(empty_scaffolds),
"empty_scaffolds": [r["slug"] for r in empty_scaffolds],
"unclassified_count": len(unclassified),
"unclassified": [r["slug"] for r in unclassified],
"explicit_none_count": len(explicit_none),
"explicit_none": [r["slug"] for r in explicit_none],
"coverage_ratio": f"{len(covered)}/{len(repos)}" if repos else "0/0",
"seeded_repos": [
{
"slug": r["slug"],
@@ -180,11 +188,23 @@ def format_gap_markdown(report: dict[str, Any]) -> str:
lines.append("- none (owner rows migrated to canonical repos)")
lines.append("")
empty_count = report.get("empty_scaffold_count", 0)
lines.append(f"## Empty scaffolds ({empty_count})")
slugs = report.get("empty_scaffolds", [])
if slugs:
for slug in slugs:
lines.append(f"**Capability coverage:** {report.get('coverage_ratio', '?')} "
"(repos with a capability or an explicit no-capability marker)")
lines.append("")
unclassified = report.get("unclassified", [])
lines.append(f"## Unclassified scaffolds ({report.get('unclassified_count', len(unclassified))})")
if unclassified:
for slug in unclassified:
lines.append(f"- `{slug}`")
else:
lines.append("- none")
lines.append("")
explicit_none = report.get("explicit_none", [])
lines.append(f"## Explicitly no-capability ({report.get('explicit_none_count', len(explicit_none))})")
if explicit_none:
for slug in explicit_none:
lines.append(f"- `{slug}`")
else:
lines.append("- none")

View File

@@ -0,0 +1,103 @@
from __future__ import annotations
import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
DEFAULT_BASE_URL = "http://127.0.0.1:8000"
TIMEOUT_SECONDS = 5
LIST_TIMEOUT_SECONDS = 20
# Observed status vocabulary (2026-07): "requested", "completed". No fixed
# enum is published by the API (free-form string field) -- treat anything
# not in this terminal set as still-open rather than assuming a closed list.
TERMINAL_STATUSES = {"completed", "cancelled", "rejected", "resolved", "closed"}
def _request(
method: str,
path: str,
base_url: str,
*,
body: dict[str, Any] | None = None,
params: dict[str, str] | None = None,
timeout: int = TIMEOUT_SECONDS,
) -> dict[str, Any] | list[Any]:
url = f"{base_url.rstrip('/')}{path}"
if params:
query = "&".join(
f"{k}={urllib.parse.quote(v)}" for k, v in params.items() if v
)
if query:
url = f"{url}?{query}"
data = json.dumps(body).encode("utf-8") if body is not None else None
request = urllib.request.Request(
url,
data=data,
method=method,
headers={"Content-Type": "application/json", "User-Agent": "reuse-surface/0.1"},
)
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.loads(response.read().decode("utf-8"))
def state_hub_reachable(base_url: str = DEFAULT_BASE_URL) -> bool:
try:
_request("GET", "/state/health", base_url)
return True
except (urllib.error.URLError, TimeoutError, OSError):
return False
def file_capability_request(
*,
title: str,
description: str,
requesting_domain: str,
requesting_agent: str,
capability_type: str = "reuse-surface-new-verdict",
requesting_workplan_id: str | None = None,
base_url: str = DEFAULT_BASE_URL,
) -> dict[str, Any] | None:
"""Files a State Hub capability request for a plan-check 'new' verdict.
Returns the created request record, or None if the hub is unreachable
(degrades gracefully -- plan-check never blocks on this)."""
if not state_hub_reachable(base_url):
return None
body = {
"title": title,
"description": description,
"capability_type": capability_type,
"requesting_domain": requesting_domain,
"requesting_agent": requesting_agent,
"requesting_workplan_id": requesting_workplan_id,
}
try:
return _request("POST", "/capability-requests/", base_url, body=body)
except (urllib.error.URLError, TimeoutError, OSError):
return None
def list_open_capability_requests(
base_url: str = DEFAULT_BASE_URL,
) -> list[dict[str, Any]] | None:
"""Returns capability requests whose status is not terminal (see
TERMINAL_STATUSES), or None if the hub is unreachable. A request can
carry a catalog_entry_id while still 'requested' (routed, not
fulfilled), so status -- not catalog_entry_id presence -- is the open
signal. The list endpoint can be slow (observed ~7s with a handful of
rows); a longer timeout than the health check is used deliberately."""
if not state_hub_reachable(base_url):
return None
try:
requests_ = _request(
"GET", "/capability-requests/", base_url, timeout=LIST_TIMEOUT_SECONDS
)
except (urllib.error.URLError, TimeoutError, OSError):
return None
if not isinstance(requests_, list):
return []
return [r for r in requests_ if r.get("status") not in TERMINAL_STATUSES]

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://reuse-surface.local/schemas/plan-check-rerank.schema.json",
"title": "PlanCheckRerankResponse",
"description": "T03 LLM rerank response. The model may only score/reorder candidate IDs it was given -- it must not invent new ones. plan_check.py enforces that separately from this schema (schema can't see the input candidate set).",
"type": "object",
"additionalProperties": false,
"required": ["candidates"],
"properties": {
"candidates": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "confidence"],
"properties": {
"id": {
"type": "string",
"pattern": "^capability\\.[a-z0-9]+(\\.[a-z0-9-]+)+$"
},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"rationale": {"type": "string"}
}
}
}
}
}

View File

@@ -0,0 +1,59 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://reuse-surface.local/schemas/plan-check-result.schema.json",
"title": "PlanCheckResult",
"type": "object",
"additionalProperties": false,
"required": ["query", "verdict", "top_score", "matches"],
"properties": {
"query": {
"type": "object",
"additionalProperties": false,
"required": ["source", "text"],
"properties": {
"source": {"type": "string", "enum": ["workplan", "intent"]},
"text": {"type": "string", "minLength": 1},
"workplan_id": {"type": ["string", "null"]},
"workplan_path": {"type": ["string", "null"]}
}
},
"verdict": {
"type": "string",
"enum": ["reuse", "extend", "new"]
},
"top_score": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"matches": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "score", "kind"],
"properties": {
"id": {
"type": "string",
"pattern": "^capability\\.[a-z0-9]+(\\.[a-z0-9-]+)+$"
},
"score": {"type": "number", "minimum": 0, "maximum": 1},
"vector": {"type": ["string", "null"]},
"owner": {"type": ["string", "null"]},
"summary": {"type": ["string", "null"]},
"kind": {
"type": "string",
"enum": ["deterministic", "llm", "related"]
}
}
}
},
"federated_index_updated": {"type": ["string", "null"]},
"federated_index_stale_warning": {"type": ["string", "null"]},
"notes": {
"type": "array",
"items": {"type": "string"}
},
"filed_capability_request": {"type": ["object", "null"]}
}
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://reuse-surface.local/schemas/reuse-event.schema.json",
"title": "ReuseEvent",
"description": "One line of registry/telemetry/plan-check-events.jsonl, or one POST /v1/reuse-events body (REUSE-WP-0019-T04). Shared schema so plan-check's local fallback and the hub's telemetry store never diverge.",
"type": "object",
"additionalProperties": false,
"required": ["ts", "consumer_repo", "verdict", "source"],
"properties": {
"ts": {"type": "string", "format": "date-time"},
"consumer_repo": {"type": "string", "minLength": 1},
"capability_id": {
"type": ["string", "null"],
"pattern": "^capability\\.[a-z0-9]+(\\.[a-z0-9-]+)+$"
},
"verdict": {
"type": "string",
"enum": ["reuse", "extend", "new"]
},
"outcome": {
"type": ["string", "null"],
"enum": ["reused", "extended", "new", "skipped", null]
},
"source": {
"type": "string",
"enum": ["plan-check", "manual", "hub"]
}
}
}

View File

@@ -195,18 +195,30 @@ capabilities:
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` |
| `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.
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.
@@ -215,8 +227,50 @@ extension is a future option.
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.
**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.
---
@@ -238,6 +292,7 @@ Non-2xx responses use:
| `unauthorized` | 401 |
| `not_found` | 404 |
| `conflict` | 409 |
| `misconfigured` | 503 |
| `compose_error` | 502 |
---
@@ -250,6 +305,8 @@ Non-2xx responses use:
| `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` |
---

170
specs/PlanCheck.md Normal file
View File

@@ -0,0 +1,170 @@
# Plan Check
**Repository:** `reuse-surface`
**Artifact:** `specs/PlanCheck.md`
**Status:** Implemented (T02 deterministic matching, T03 LLM rerank, T04
State Hub bridge, T05 ecosystem convention, T06 docs/CI all shipped)
**Schema:** `schemas/plan-check-result.schema.json`
---
## 1. Purpose
`plan-check` closes the consumption loop the registry has lacked since
inception: nothing today nudges an agent planning work in a sibling repo to
query the federated capability index before building. The registry has been
write-mostly. `plan-check` is the query-before-build step — advisory, not a
gate — that matches a draft workplan or a free-text intent against the
federated index and returns a verdict: reuse it, extend it, or it's genuinely
new.
This spec covers the deterministic matching core (T02) and its interfaces
with the optional LLM rerank (T03) and the State Hub capability-request
bridge (T04). It does not cover the ecosystem rollout (T05) — the
session-protocol convention is designed once this spec is implemented and
dogfooded in this repo.
## 2. Design principles
1. **Deterministic core, LLM assist optional.** Keyword/scope/relation
matching against `registry/indexes/federated.yaml` works with no external
dependency. `LLM_CONNECT_URL` unlocks a semantic rerank pass; its absence
degrades gracefully, never blocks.
2. **Advisory, not blocking.** `plan-check` never fails a workplan into
existence or refuses to let one be created. Adoption comes from the tool
being useful and from convention (REUSE-WP-0018-T05), not from a gate.
3. **Deterministic matches always rank first.** Whatever the LLM rerank
proposes, it is listed after — and clearly labeled apart from — the
deterministic candidates, so a human or agent can trust the base result
even with `--no-llm`.
4. **Every invocation is a data point.** `--record-outcome` writes an
append-only fact. This is the raw material for REUSE-WP-0019's reuse
telemetry — `plan-check` and telemetry share one event schema from day
one so nothing needs migrating later.
## 3. Input model
Two input shapes, mutually exclusive:
| Input | How it's read |
|---|---|
| Workplan file | Parsed like `registry_update.py`'s git-diff signal collector: YAML frontmatter (`---`-delimited) plus the Markdown body. `title`, the one-liner in the intro paragraph, and any `## Problem statement` / `## Core Idea` heading content feed the match blob. |
| `--intent "free text"` | Used verbatim as the match blob. |
Both normalize to the same internal `MatchQuery { text: str, tokens: set[str] }`
before matching — the matcher does not care which input shape it came from.
## 4. Matching pipeline
Reuses the token-Jaccard approach already proven in `overlaps.py`
(`TOKEN_RE`, `_tokens`) rather than inventing a second scoring method in the
same codebase:
1. **Tokenize** the query blob with the existing `TOKEN_RE`.
2. **Score** against every federated capability's blob (`name` + `summary` +
`tags` + `discovery.intent` + `discovery.includes`, mirroring
`overlaps._entry_blob`) via Jaccard similarity.
3. **Rank signal, not just tie-break:** among candidates within
`--tie-window` (default 0.05) of the top score, prefer the entry with the
higher discovery/availability vector (a more mature capability is a safer
reuse bet at equal textual match).
4. **Relation expansion:** if a top candidate has `relations.supports` or
`relations.related_to` entries, surface them as secondary candidates
labeled `related` rather than silently dropped.
## 5. Verdict model
| Verdict | Condition | Meaning |
|---|---|---|
| `reuse` | top score ≥ `--reuse-threshold` (default 0.45) | An existing capability already covers this need — link it, don't rebuild it. |
| `extend` | top score in `[--extend-threshold, --reuse-threshold)` (default 0.220.45) | Scope overlaps a capability closely enough that extending it is very likely cheaper than a new one — surfaced with an explicit "consider extending" framing. |
| `new` | top score < `--extend-threshold`, or no federated capabilities exist | No good match. Proceed; optionally file a capability request (T04). |
Thresholds are CLI flags, not hardcoded, because the right cutoff will drift
as coverage and entry quality improve (see REUSE-WP-0017). Defaults come from
the same threshold reasoning as `overlaps.py`'s `--threshold 0.28` default,
shifted since plan-check matches short intent text against short summaries
rather than long entry-to-entry blobs.
## 6. Output
### Markdown (TTY default)
```text
# Plan check: reuse | extend | new
**Query:** <workplan title or intent text>
## Top match
- `capability.infotech.issue-tracking` (score 0.52, D4/A2/C2/R1) — issue-core
> Unified Python/CLI interface for issue tracking across Gitea, GitHub, and GitLab...
## Other candidates
- ...
## Related (via relations.*)
- ...
Verdict: REUSE — link this capability instead of building new.
```
### JSON (`--format json`, agent-consumable)
Validated against `schemas/plan-check-result.schema.json`:
```json
{
"query": {"source": "workplan|intent", "text": "...", "workplan_id": "..."},
"verdict": "reuse|extend|new",
"top_score": 0.52,
"matches": [
{"id": "capability.infotech.issue-tracking", "score": 0.52, "vector": "D4/A2/C2/R1", "owner": "issue-core", "kind": "deterministic|llm|related"}
],
"federated_index_updated": "2026-07-06",
"federated_index_stale_warning": null
}
```
`federated_index_stale_warning` is set when `federated.yaml`'s `updated`
field is older than 14 days — a cheap freshness signal until
REUSE-WP-0019's automatic recompose lands.
## 7. Outcome recording (`--record-outcome`)
Append-only JSONL under `registry/telemetry/plan-check-events.jsonl`
(reuse-surface's own copy; sibling repos get their own via the same schema
when they adopt T05). One line per invocation:
```json
{"ts": "2026-07-07T10:00:00Z", "consumer_repo": "reuse-surface", "capability_id": "capability.infotech.issue-tracking", "verdict": "reuse", "outcome": "reused|extended|new|skipped", "source": "plan-check"}
```
This schema is deliberately identical to the reuse-event schema
REUSE-WP-0019-T04 will implement server-side — `plan-check`'s local JSONL
file is the fallback path when the hub is unreachable, and the same record
shape posts to `POST /v1/reuse-events` once T04/WP-0019 exist. No local
telemetry write ever blocks the primary command.
## 8. Relationship to T03 (LLM rerank) and T04 (State Hub bridge)
- **T03** adds an optional post-pass: send the deterministic top-N candidates
plus the query text to `llm-connect` for a semantic confidence score and
possible reordering *within* the deterministic candidate set. It does not
invent new candidates outside what deterministic matching already found —
keeps the "deterministic matches always rank first" guarantee simple to
reason about. Schema-constrained JSON response, mirrors the
`maintain_llm.py` pattern (graceful skip on missing `LLM_CONNECT_URL`,
reject malformed responses rather than guess).
- **T04** wires `new` verdicts to `POST /messages/` (State Hub
`request_capability`-equivalent) and reads back open capability requests
for `report gaps` to list unmatched. Deferred to its own task since it
depends on State Hub API shapes this spec does not need to pin down yet.
## 9. Non-goals
- Blocking or gating workplan creation (§2.2).
- Embedding-based / vector-similarity matching — token-Jaccard is the
deterministic core for consistency with `overlaps.py`; embeddings are an
LLM-rerank concern (T03), not a second deterministic path.
- Editing sibling repos' rules files directly — that's the template
propagation mechanism's job (T05), not this tool's.

View File

@@ -0,0 +1,24 @@
---
# Copy this file to registry/NO_CAPABILITIES.md in the target repo.
# It marks the repo as reviewed with no reusable capability to register,
# converting an "empty scaffold" from ambiguous to informative.
repo: <slug>
reason: >
One or two sentences on why this repo has nothing to register — e.g.
personal experiment, fork with no independent scope, docs/canon-only,
probe/spike not intended for reuse.
reviewed: "YYYY-MM-DD"
reviewed_by: <person-or-agent>
revisit: >
Optional. Note a condition or date under which this repo should be
reclassified (e.g. "revisit if the probe becomes a maintained service").
---
# No reusable capability
This repo was reviewed for the `reuse-surface` capability registry
(REUSE-WP-0017 coverage campaign) and has no capability to register at this
time. See the `reason` field above.
Set `capability_status: none` for this repo's row in
`local-repo-roster.yaml` when this file lands.

188
tests/test_forge_host.py Normal file
View File

@@ -0,0 +1,188 @@
from __future__ import annotations
import pytest
from reuse_surface.forge_host import (
derive_raw_url,
forge_base_url,
migrate_source_host,
parse_raw_url,
rewrite_url_host,
)
def test_parse_raw_url_legacy_form():
parts = parse_raw_url("https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml")
assert parts.scheme == "https"
assert parts.host == "gitea.coulomb.social"
assert parts.org == "coulomb"
assert parts.repo == "activity-core"
assert parts.branch == "main"
assert parts.path == "registry/indexes/capabilities.yaml"
def test_parse_raw_url_branch_qualified_form():
parts = parse_raw_url("https://forgejo.coulomb.social/coulomb/core-hub/raw/branch/main/registry/indexes/capabilities.yaml")
assert parts.host == "forgejo.coulomb.social"
assert parts.repo == "core-hub"
assert parts.branch == "main"
def test_parse_raw_url_rejects_unrecognized():
with pytest.raises(ValueError):
parse_raw_url("https://example.com/not/a/raw/url")
def test_derive_raw_url_uses_branch_qualified_form():
url = derive_raw_url("https://forgejo.coulomb.social", "coulomb", "core-hub")
assert url == "https://forgejo.coulomb.social/coulomb/core-hub/raw/branch/main/registry/indexes/capabilities.yaml"
def test_derive_raw_url_strips_trailing_slash():
url = derive_raw_url("https://forgejo.coulomb.social/", "coulomb", "core-hub")
assert url.startswith("https://forgejo.coulomb.social/coulomb/")
def test_rewrite_url_host_swaps_host_and_normalizes_form():
old = "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"
new = rewrite_url_host(old, "https://forgejo.coulomb.social")
assert new == "https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml"
def test_forge_base_url_env_fallback(monkeypatch):
monkeypatch.setenv("REUSE_SURFACE_FORGE_BASE_URL", "https://forgejo.coulomb.social")
assert forge_base_url() == "https://forgejo.coulomb.social"
def test_forge_base_url_none_when_unset(monkeypatch):
monkeypatch.delenv("REUSE_SURFACE_FORGE_BASE_URL", raising=False)
assert forge_base_url() is None
def test_forge_base_url_explicit_wins(monkeypatch):
monkeypatch.setenv("REUSE_SURFACE_FORGE_BASE_URL", "https://env-value")
assert forge_base_url("https://explicit-value") == "https://explicit-value"
def test_migrate_source_host_mutates_matching_entry(monkeypatch):
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (True, 200))
sources = [
{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"},
{"repo": "other-repo", "url": "https://gitea.coulomb.social/coulomb/other-repo/raw/main/registry/indexes/capabilities.yaml"},
]
report = migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social")
assert report["verified"] is True
assert sources[0]["url"] == "https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml"
# other entries untouched
assert sources[1]["url"] == "https://gitea.coulomb.social/coulomb/other-repo/raw/main/registry/indexes/capabilities.yaml"
def test_migrate_source_host_repo_not_found():
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
with pytest.raises(ValueError, match="not found"):
migrate_source_host(sources, "does-not-exist", "https://forgejo.coulomb.social")
def test_migrate_source_host_refuses_unverified_url(monkeypatch):
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (False, 404))
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
original_url = sources[0]["url"]
with pytest.raises(ValueError, match="did not resolve"):
migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social")
# must not have mutated the entry when verification failed
assert sources[0]["url"] == original_url
def test_migrate_source_host_skips_verification_when_disabled():
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
report = migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social", verify=False)
assert report["verified"] is None
assert sources[0]["url"].startswith("https://forgejo.coulomb.social/")
def test_cmd_federation_migrate_host_dry_run(tmp_path, monkeypatch):
from reuse_surface.cli import main
sources_path = tmp_path / "sources.yaml"
sources_path.write_text(
"version: 1\n"
"domain: helix_forge\n"
"collision_policy: warn\n"
"sources:\n"
"- repo: activity-core\n"
" url: https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml\n"
" enabled: true\n"
)
monkeypatch.setattr("reuse_surface.cli.migrate_source_host", lambda sources, repo, to, verify=True: {
"repo": repo, "old_url": sources[0]["url"], "new_url": "https://forgejo.coulomb.social/x", "verified": True,
})
exit_code = main([
"federation", "migrate-host",
"--repo", "activity-core",
"--to", "https://forgejo.coulomb.social",
"--sources", str(sources_path),
"--dry-run",
])
assert exit_code == 0
# dry-run must not write
assert "gitea.coulomb.social" in sources_path.read_text()
def test_cmd_federation_migrate_host_writes_file(tmp_path, monkeypatch):
from reuse_surface.cli import main
sources_path = tmp_path / "sources.yaml"
sources_path.write_text(
"version: 1\n"
"domain: helix_forge\n"
"collision_policy: warn\n"
"sources:\n"
"- repo: activity-core\n"
" url: https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml\n"
" enabled: true\n"
)
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (True, 200))
exit_code = main([
"federation", "migrate-host",
"--repo", "activity-core",
"--to", "https://forgejo.coulomb.social",
"--sources", str(sources_path),
])
assert exit_code == 0
written = sources_path.read_text()
assert "forgejo.coulomb.social" in written
assert "gitea.coulomb.social" not in written
def test_cmd_federation_migrate_host_repo_not_found_errors(tmp_path):
from reuse_surface.cli import main
sources_path = tmp_path / "sources.yaml"
sources_path.write_text("version: 1\ndomain: helix_forge\ncollision_policy: warn\nsources: []\n")
exit_code = main([
"federation", "migrate-host",
"--repo", "does-not-exist",
"--to", "https://forgejo.coulomb.social",
"--sources", str(sources_path),
"--dry-run",
])
assert exit_code == 1
def test_cmd_federation_migrate_host_from_mismatch_errors(tmp_path):
from reuse_surface.cli import main
sources_path = tmp_path / "sources.yaml"
sources_path.write_text(
"version: 1\ndomain: helix_forge\ncollision_policy: warn\n"
"sources:\n- repo: activity-core\n url: https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml\n"
)
exit_code = main([
"federation", "migrate-host",
"--repo", "activity-core",
"--to", "https://forgejo.coulomb.social",
"--from", "gitea.coulomb.social",
"--sources", str(sources_path),
"--dry-run",
])
assert exit_code == 1

View File

@@ -131,3 +131,184 @@ def test_store_validation(tmp_path):
store = HubStore(tmp_path / "hub.db")
with pytest.raises(ValueError):
store.create_repo({"repo": "BAD", "url": "ftp://x", "domain": "helix_forge"})
# --- T02: composed_at / stale tracking ---
def test_compose_state_starts_unset(tmp_path):
store = HubStore(tmp_path / "hub.db")
state = store.get_compose_state()
assert state == {"composed_at": None, "stale": False}
def test_record_compose_sets_timestamp_and_clears_stale(tmp_path):
store = HubStore(tmp_path / "hub.db")
store.mark_stale()
assert store.get_compose_state()["stale"] is True
composed_at = store.record_compose()
state = store.get_compose_state()
assert state["composed_at"] == composed_at
assert state["stale"] is False
def test_plain_get_does_not_clear_stale(hub_client, monkeypatch):
hub_client.post(
"/v1/repos",
json={"repo": "remote-repo", "url": "https://example.com/capabilities.yaml", "domain": "helix_forge"},
headers={"Authorization": "Bearer test-token"},
)
payload = REMOTE_INDEX.encode("utf-8")
class FakeResponse:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def read(self):
return payload
with patch("urllib.request.urlopen", return_value=FakeResponse()):
# force a real compose so composed_at/stale are established
first = hub_client.get("/v1/federated?refresh=true")
assert first.json()["stale"] is False
composed_at_1 = first.json()["composed_at"]
# a plain GET (no refresh) must not report itself as freshly composed
second = hub_client.get("/v1/federated")
assert second.json()["composed_at"] == composed_at_1
assert second.json()["stale"] is False
def test_get_federated_reports_stale_after_mark(hub_client, monkeypatch):
hub_client.post(
"/v1/repos",
json={"repo": "remote-repo", "url": "https://example.com/capabilities.yaml", "domain": "helix_forge"},
headers={"Authorization": "Bearer test-token"},
)
payload = REMOTE_INDEX.encode("utf-8")
class FakeResponse:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def read(self):
return payload
with patch("urllib.request.urlopen", return_value=FakeResponse()):
hub_client.get("/v1/federated?refresh=true")
from reuse_surface.hub.store import HubStore as _HubStore
db_path = os.environ["REUSE_SURFACE_DB"]
_HubStore(Path(db_path)).mark_stale()
response = hub_client.get("/v1/federated")
assert response.json()["stale"] is True
# --- T02: Forgejo webhook receiver ---
WEBHOOK_SECRET = "test-webhook-secret"
def _sign(body: bytes, secret: str = WEBHOOK_SECRET) -> str:
import hashlib
import hmac as hmac_module
return hmac_module.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
@pytest.fixture
def webhook_client(hub_client, monkeypatch):
monkeypatch.setenv("REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET", WEBHOOK_SECRET)
return hub_client
def test_webhook_rejects_missing_signature(webhook_client):
response = webhook_client.post("/v1/webhooks/forgejo", json={"commits": []})
assert response.status_code == 401
def test_webhook_rejects_wrong_signature(webhook_client):
body = json.dumps({"commits": []}).encode("utf-8")
response = webhook_client.post(
"/v1/webhooks/forgejo",
content=body,
headers={"X-Forgejo-Signature": "0" * 64, "Content-Type": "application/json"},
)
assert response.status_code == 401
def test_webhook_rejects_when_secret_not_configured(hub_client):
body = json.dumps({"commits": []}).encode("utf-8")
response = hub_client.post(
"/v1/webhooks/forgejo",
content=body,
headers={"X-Forgejo-Signature": _sign(body), "Content-Type": "application/json"},
)
assert response.status_code == 503
def test_webhook_ignores_push_without_registry_change(webhook_client):
body = json.dumps({"commits": [{"added": ["README.md"], "modified": [], "removed": []}]}).encode("utf-8")
response = webhook_client.post(
"/v1/webhooks/forgejo",
content=body,
headers={"X-Forgejo-Signature": _sign(body), "Content-Type": "application/json"},
)
assert response.status_code == 200
assert response.json()["accepted"] is False
def test_webhook_triggers_recompose_on_registry_change(webhook_client, monkeypatch):
webhook_client.post(
"/v1/repos",
json={"repo": "remote-repo", "url": "https://example.com/capabilities.yaml", "domain": "helix_forge"},
headers={"Authorization": "Bearer test-token"},
)
payload_bytes = REMOTE_INDEX.encode("utf-8")
class FakeResponse:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def read(self):
return payload_bytes
body = json.dumps(
{"commits": [{"added": ["registry/indexes/capabilities.yaml"], "modified": [], "removed": []}]}
).encode("utf-8")
with patch("urllib.request.urlopen", return_value=FakeResponse()):
response = webhook_client.post(
"/v1/webhooks/forgejo",
content=body,
headers={"X-Forgejo-Signature": _sign(body), "Content-Type": "application/json"},
)
assert response.status_code == 200
assert response.json()["accepted"] is True
assert response.json()["composed_at"]
# federated index should now report the newly composed data, not stale
follow_up = webhook_client.get("/v1/federated")
assert follow_up.json()["stale"] is False
ids = {item["id"] for item in follow_up.json()["capabilities"]}
assert "capability.remote.sample" in ids
def test_webhook_accepts_gitea_signature_header(webhook_client):
body = json.dumps({"commits": [{"added": ["README.md"], "modified": [], "removed": []}]}).encode("utf-8")
response = webhook_client.post(
"/v1/webhooks/forgejo",
content=body,
headers={"X-Gitea-Signature": _sign(body), "Content-Type": "application/json"},
)
assert response.status_code == 200

351
tests/test_plan_check.py Normal file
View File

@@ -0,0 +1,351 @@
from __future__ import annotations
import json
from reuse_surface.plan_check import (
Match,
MatchQuery,
apply_rerank,
load_query_from_intent,
load_query_from_workplan,
maybe_file_capability_request,
match_query,
record_outcome,
request_rerank,
run_plan_check,
verdict_for_score,
)
SAMPLE_CAPABILITIES = [
{
"id": "capability.infotech.issue-tracking",
"name": "Universal Issue Tracking Coordination",
"summary": "Unified interface for issue tracking coordination across Gitea, GitHub, GitLab.",
"vector": "D4 / A2 / C2 / R1",
"owner": "issue-core",
"tags": ["issue-tracking", "coordination"],
},
{
"id": "capability.audit.event-retain",
"name": "Audit Event Retention",
"summary": "Collect, normalize, retain, and search audit events with integrity evidence across tenants.",
"vector": "D4 / A2 / C2 / R1",
"owner": "audit-core",
"tags": ["audit"],
},
]
def test_verdict_thresholds():
assert verdict_for_score(0.5) == "reuse"
assert verdict_for_score(0.3) == "extend"
assert verdict_for_score(0.1) == "new"
assert verdict_for_score(0.45) == "reuse"
assert verdict_for_score(0.22) == "extend"
def test_match_query_finds_close_match():
query = load_query_from_intent(
"unified issue tracking coordination across Gitea GitHub GitLab"
)
matches = match_query(query, SAMPLE_CAPABILITIES)
assert matches
assert matches[0].id == "capability.infotech.issue-tracking"
assert matches[0].score > 0.3
def test_match_query_empty_when_no_overlap():
query = load_query_from_intent("something entirely unrelated xyzzy plugh")
matches = match_query(query, SAMPLE_CAPABILITIES)
assert matches == []
def test_match_query_no_capabilities():
query = load_query_from_intent("anything")
assert match_query(query, []) == []
def test_load_query_from_workplan(tmp_path):
workplan = tmp_path / "TEST-WP-0001-thing.md"
workplan.write_text(
"""---
id: TEST-WP-0001
title: "Unified issue tracking coordination"
status: proposed
---
# Unified issue tracking coordination
## Core Idea
Build a single interface for issue tracking across Gitea, GitHub, and GitLab.
"""
)
query = load_query_from_workplan(workplan)
assert query.source == "workplan"
assert query.workplan_id == "TEST-WP-0001"
assert "issue tracking" in query.text.lower()
matches = match_query(query, SAMPLE_CAPABILITIES)
assert matches[0].id == "capability.infotech.issue-tracking"
def test_run_plan_check_reuse_verdict(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-06"),
)
query = MatchQuery(
source="intent",
text="unified issue tracking coordination gitea github gitlab",
tokens={"unified", "issue", "tracking", "coordination", "gitea", "github", "gitlab"},
)
result = run_plan_check(query)
assert result["verdict"] == "reuse"
assert result["matches"][0]["id"] == "capability.infotech.issue-tracking"
assert result["federated_index_stale_warning"] is None
def test_run_plan_check_stale_warning(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2020-01-01"),
)
query = load_query_from_intent("nothing matches this at all zzz")
result = run_plan_check(query)
assert result["verdict"] == "new"
assert "days old" in result["federated_index_stale_warning"]
def test_record_outcome_appends_jsonl(tmp_path, monkeypatch):
telemetry_path = tmp_path / "plan-check-events.jsonl"
monkeypatch.setattr("reuse_surface.plan_check.TELEMETRY_PATH", telemetry_path)
result = {
"verdict": "reuse",
"matches": [{"id": "capability.infotech.issue-tracking"}],
}
record_outcome(result, "reused", consumer_repo="some-repo")
lines = telemetry_path.read_text().splitlines()
assert len(lines) == 1
event = json.loads(lines[0])
assert event["consumer_repo"] == "some-repo"
assert event["capability_id"] == "capability.infotech.issue-tracking"
assert event["outcome"] == "reused"
assert event["source"] == "plan-check"
def test_maybe_file_capability_request_only_on_new_verdict(monkeypatch):
called = []
monkeypatch.setattr(
"reuse_surface.plan_check.file_capability_request",
lambda **kwargs: called.append(kwargs) or {"id": "req-1"},
)
reuse_result = {"verdict": "reuse", "query": {"text": "x", "workplan_id": None}}
assert maybe_file_capability_request(
reuse_result, requesting_domain="infotech", requesting_agent="a"
) is None
assert called == []
new_result = {"verdict": "new", "query": {"text": "some new intent", "workplan_id": None}}
filed = maybe_file_capability_request(
new_result, requesting_domain="infotech", requesting_agent="a"
)
assert filed == {"id": "req-1"}
assert called[0]["requesting_domain"] == "infotech"
def test_cmd_plan_check_file_request_flag(monkeypatch):
from reuse_surface.cli import main
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-06"),
)
filed_calls = []
monkeypatch.setattr(
"reuse_surface.cli.maybe_file_capability_request",
lambda result, **kwargs: filed_calls.append(kwargs) or {"id": "req-9"},
)
exit_code = main(
["plan-check", "--intent", "totally unrelated xyzzy plugh", "--file-request"]
)
assert exit_code == 0
assert filed_calls
def test_cmd_plan_check_intent_json(monkeypatch):
from reuse_surface.cli import main
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-06"),
)
exit_code = main(
["plan-check", "--intent", "issue tracking gitea github gitlab", "--format", "json"]
)
assert exit_code == 0
def test_cmd_plan_check_requires_input():
from reuse_surface.cli import main
exit_code = main(["plan-check"])
assert exit_code == 1
def test_cmd_plan_check_rejects_both_inputs(tmp_path):
from reuse_surface.cli import main
workplan = tmp_path / "x.md"
workplan.write_text("---\nid: X\n---\nbody")
exit_code = main(["plan-check", str(workplan), "--intent", "also this"])
assert exit_code == 1
# --- T03: LLM rerank ---
SAMPLE_MATCHES = [
Match(
id="capability.infotech.issue-tracking",
score=0.36,
kind="deterministic",
vector="D4 / A2 / C2 / R1",
owner="issue-core",
summary="Unified interface for issue tracking.",
),
Match(
id="capability.audit.event-retain",
score=0.12,
kind="deterministic",
vector="D4 / A2 / C2 / R1",
owner="audit-core",
summary="Collect and retain audit events.",
),
]
def test_request_rerank_valid_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{
"candidates": [
{"id": "capability.infotech.issue-tracking", "confidence": 0.9, "rationale": "strong match"},
{"id": "capability.audit.event-retain", "confidence": 0.1, "rationale": "unrelated"},
]
}
),
)
query = load_query_from_intent("issue tracking across platforms")
result = request_rerank(query, SAMPLE_MATCHES)
assert len(result) == 2
assert result[0]["confidence"] == 0.9
def test_request_rerank_rejects_malformed_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps({"candidates": [{"id": "capability.infotech.issue-tracking"}]}),
)
query = load_query_from_intent("issue tracking across platforms")
try:
request_rerank(query, SAMPLE_MATCHES)
assert False, "expected ValueError for missing confidence field"
except ValueError as exc:
assert "schema validation failed" in str(exc)
def test_request_rerank_rejects_non_json_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: "not json at all",
)
query = load_query_from_intent("issue tracking across platforms")
try:
request_rerank(query, SAMPLE_MATCHES)
assert False, "expected ValueError for non-JSON response"
except ValueError:
pass
def test_request_rerank_ignores_invented_ids(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{
"candidates": [
{"id": "capability.infotech.issue-tracking", "confidence": 0.8},
{"id": "capability.madeup.not-real", "confidence": 0.99},
]
}
),
)
query = load_query_from_intent("issue tracking across platforms")
result = request_rerank(query, SAMPLE_MATCHES)
assert [c["id"] for c in result] == ["capability.infotech.issue-tracking"]
def test_apply_rerank_appends_after_deterministic_matches():
rerank_result = [
{"id": "capability.audit.event-retain", "confidence": 0.95, "rationale": "actually a great fit"},
]
combined = apply_rerank(SAMPLE_MATCHES, rerank_result)
# Deterministic matches keep their original order and scores untouched.
assert combined[0].id == "capability.infotech.issue-tracking"
assert combined[0].score == 0.36
assert combined[0].kind == "deterministic"
assert combined[1].id == "capability.audit.event-retain"
assert combined[1].score == 0.12
assert combined[1].kind == "deterministic"
# LLM entry is appended after, separately labeled.
assert combined[2].id == "capability.audit.event-retain"
assert combined[2].score == 0.95
assert combined[2].kind == "llm"
def test_run_plan_check_skips_llm_gracefully_when_unset(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.delenv("LLM_CONNECT_URL", raising=False)
query = load_query_from_intent("issue tracking gitea github gitlab")
result = run_plan_check(query)
assert result["verdict"] in {"reuse", "extend"}
assert any("LLM_CONNECT_URL not set" in n for n in result.get("notes", []))
assert all(m["kind"] == "deterministic" for m in result["matches"])
def test_run_plan_check_no_llm_flag_skips_without_attempting(monkeypatch):
called = []
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.setattr(
"reuse_surface.plan_check.request_rerank",
lambda *a, **k: called.append(1) or [],
)
query = load_query_from_intent("issue tracking gitea github gitlab")
run_plan_check(query, use_llm=False)
assert called == []
def test_run_plan_check_integrates_successful_rerank(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{"candidates": [{"id": "capability.infotech.issue-tracking", "confidence": 0.77}]}
),
)
query = load_query_from_intent("issue tracking gitea github gitlab")
result = run_plan_check(query)
assert "notes" not in result or not result["notes"]
llm_entries = [m for m in result["matches"] if m["kind"] == "llm"]
assert len(llm_entries) == 1
assert llm_entries[0]["score"] == 0.77
# deterministic top match is still first and unaffected
assert result["matches"][0]["kind"] == "deterministic"

View File

@@ -96,9 +96,38 @@ def test_collect_gap_report_from_roster():
root = Path(__file__).resolve().parent.parent
roster = root / "registry/federation/local-repo-roster.yaml"
report = collect_gap_report(roster)
assert report["summary"]["total"] == 60
assert report["summary"]["total"] == 62
assert len(report["publish_fail"]) == 0
assert report["empty_scaffold_count"] >= 40
assert report["unclassified_count"] + report["explicit_none_count"] == report["empty_scaffold_count"]
assert "/" in report["coverage_ratio"]
def test_collect_gap_report_splits_unclassified_and_explicit_none(tmp_path):
roster_path = tmp_path / "roster.yaml"
roster_path.write_text(
"""
summary:
total: 3
repos:
- slug: has-repo
status: established
capability_count: 1
capability_status: has
- slug: none-repo
status: established
capability_count: 0
capability_status: none
- slug: pending-repo
status: established
capability_count: 0
capability_status: pending
"""
)
report = collect_gap_report(roster_path, index={"capabilities": []})
assert report["unclassified"] == ["pending-repo"]
assert report["explicit_none"] == ["none-repo"]
assert report["empty_scaffold_count"] == 2
assert report["coverage_ratio"] == "2/3"
def test_format_gap_markdown_lists_publish_fail():
@@ -108,6 +137,11 @@ def test_format_gap_markdown_lists_publish_fail():
"publish_fail": [{"slug": "inter-hub", "publish_note": "missing repo"}],
"empty_scaffold_count": 1,
"empty_scaffolds": ["ops-bridge"],
"unclassified_count": 1,
"unclassified": ["ops-bridge"],
"explicit_none_count": 0,
"explicit_none": [],
"coverage_ratio": "59/60",
"seeded_repos": [],
"dedup_pending_local_owners": [],
"local_capability_count": 2,
@@ -124,6 +158,29 @@ def test_cmd_report_gaps_json(monkeypatch):
assert exit_code == 0
def test_cmd_report_gaps_check_capability_requests_unreachable(monkeypatch):
from reuse_surface.cli import main
monkeypatch.setattr(
"reuse_surface.cli.list_open_capability_requests", lambda: None
)
exit_code = main(["report", "gaps", "--check-capability-requests"])
assert exit_code == 0
def test_cmd_report_gaps_check_capability_requests_json(monkeypatch):
from reuse_surface.cli import main
monkeypatch.setattr(
"reuse_surface.cli.list_open_capability_requests",
lambda: [{"id": "r1", "title": "Need X", "requesting_domain_slug": "infotech"}],
)
exit_code = main(
["report", "gaps", "--check-capability-requests", "--format", "json"]
)
assert exit_code == 0
def test_cmd_report_cohorts_markdown(monkeypatch):
from reuse_surface.cli import main

View File

@@ -0,0 +1,66 @@
from __future__ import annotations
import urllib.error
from reuse_surface import statehub_bridge
def test_state_hub_reachable_true(monkeypatch):
monkeypatch.setattr(statehub_bridge, "_request", lambda *a, **k: {"status": "ok"})
assert statehub_bridge.state_hub_reachable() is True
def test_state_hub_reachable_false_on_error(monkeypatch):
def raise_error(*args, **kwargs):
raise urllib.error.URLError("no route")
monkeypatch.setattr(statehub_bridge, "_request", raise_error)
assert statehub_bridge.state_hub_reachable() is False
def test_file_capability_request_skips_when_unreachable(monkeypatch):
monkeypatch.setattr(statehub_bridge, "state_hub_reachable", lambda base_url=None: False)
result = statehub_bridge.file_capability_request(
title="t", description="d", requesting_domain="infotech", requesting_agent="a"
)
assert result is None
def test_file_capability_request_posts_body(monkeypatch):
captured = {}
def fake_request(method, path, base_url, *, body=None, params=None, timeout=None):
captured["method"] = method
captured["path"] = path
captured["body"] = body
return {"id": "abc-123", **body}
monkeypatch.setattr(statehub_bridge, "state_hub_reachable", lambda base_url=None: True)
monkeypatch.setattr(statehub_bridge, "_request", fake_request)
result = statehub_bridge.file_capability_request(
title="Need X", description="desc", requesting_domain="infotech", requesting_agent="reuse-surface"
)
assert captured["method"] == "POST"
assert captured["path"] == "/capability-requests/"
assert captured["body"]["title"] == "Need X"
assert result["id"] == "abc-123"
def test_list_open_capability_requests_filters_terminal_status(monkeypatch):
monkeypatch.setattr(statehub_bridge, "state_hub_reachable", lambda base_url=None: True)
monkeypatch.setattr(
statehub_bridge,
"_request",
lambda *a, **k: [
{"id": "1", "status": "requested"},
{"id": "2", "status": "completed"},
{"id": "3", "status": "requested", "catalog_entry_id": "routed-but-open"},
],
)
result = statehub_bridge.list_open_capability_requests()
assert {r["id"] for r in result} == {"1", "3"}
def test_list_open_capability_requests_none_when_unreachable(monkeypatch):
monkeypatch.setattr(statehub_bridge, "state_hub_reachable", lambda base_url=None: False)
assert statehub_bridge.list_open_capability_requests() is None

View File

@@ -29,8 +29,8 @@ def test_collect_roster_stats_federation_ready():
root = Path(__file__).resolve().parent.parent
roster = root / "registry/federation/local-repo-roster.yaml"
stats = collect_roster_stats(roster, federation_ready=True)
assert stats["counts"]["total"] == 60
assert stats["counts"]["established"] == 60
assert stats["counts"]["total"] == 62
assert stats["counts"]["established"] == 62
assert "federation_readiness" in stats
text = format_roster_stats_markdown(stats)
assert "publish pass ratio" in text

82
tests/test_webhooks.py Normal file
View File

@@ -0,0 +1,82 @@
from __future__ import annotations
import hashlib
import hmac
from reuse_surface.hub.webhooks import push_touches_registry_index, verify_signature
SECRET = "shh"
def _sign(body: bytes, secret: str = SECRET) -> str:
return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
def test_verify_signature_valid():
body = b'{"commits": []}'
assert verify_signature(SECRET, body, _sign(body)) is True
def test_verify_signature_wrong_secret():
body = b'{"commits": []}'
assert verify_signature("different-secret", body, _sign(body)) is False
def test_verify_signature_tampered_body():
body = b'{"commits": []}'
signature = _sign(body)
assert verify_signature(SECRET, b'{"commits": [1]}', signature) is False
def test_verify_signature_missing_signature():
assert verify_signature(SECRET, b"{}", None) is False
def test_verify_signature_empty_secret_rejects():
body = b"{}"
# Even if a caller accidentally computed a signature with an empty
# secret, verify_signature must not accept it -- an unconfigured
# secret is not the same as "verification not needed".
assert verify_signature("", body, _sign(body, secret="")) is False
def test_push_touches_registry_index_added():
payload = {"commits": [{"added": ["registry/indexes/capabilities.yaml"], "modified": [], "removed": []}]}
assert push_touches_registry_index(payload) is True
def test_push_touches_registry_index_modified():
payload = {"commits": [{"added": [], "modified": ["registry/indexes/federated.yaml"], "removed": []}]}
assert push_touches_registry_index(payload) is True
def test_push_touches_registry_index_removed():
payload = {"commits": [{"added": [], "modified": [], "removed": ["registry/indexes/capabilities.yaml"]}]}
assert push_touches_registry_index(payload) is True
def test_push_does_not_touch_registry_index():
payload = {"commits": [{"added": ["README.md"], "modified": ["src/main.py"], "removed": []}]}
assert push_touches_registry_index(payload) is False
def test_push_touches_registry_index_across_multiple_commits():
payload = {
"commits": [
{"added": ["README.md"], "modified": [], "removed": []},
{"added": [], "modified": ["registry/indexes/capabilities.yaml"], "removed": []},
]
}
assert push_touches_registry_index(payload) is True
def test_push_touches_registry_index_empty_commits():
assert push_touches_registry_index({"commits": []}) is False
assert push_touches_registry_index({}) is False
def test_push_touches_registry_index_ignores_similarly_named_paths():
# a path that merely starts with "registry" but isn't under
# registry/indexes/ must not match
payload = {"commits": [{"added": ["registry/capabilities/capability.foo.md"], "modified": [], "removed": []}]}
assert push_touches_registry_index(payload) is False

View File

@@ -51,6 +51,39 @@ reuse-surface overlaps
reuse-surface overlaps --threshold 0.35
```
### plan-check
Query-before-build check (REUSE-WP-0018): match a draft workplan or a free
intent against the federated capability index before starting new work.
Deterministic token matching against `registry/indexes/federated.yaml`;
advisory only — never blocks workplan creation. See `specs/PlanCheck.md`.
```bash
reuse-surface plan-check workplans/XXX-WP-0042-something.md
reuse-surface plan-check --intent "parse invoices and file evidence"
reuse-surface plan-check --intent "..." --format json
reuse-surface plan-check --intent "..." --record-outcome reused
reuse-surface plan-check --intent "..." --file-request --requesting-domain infotech
export LLM_CONNECT_URL=http://127.0.0.1:8080 # optional, enables semantic rerank
reuse-surface plan-check --intent "..." --no-llm # skip the rerank pass
reuse-surface plan-check --intent "..." --llm-url http://127.0.0.1:8080
```
`reuse|extend|new` verdict from `--reuse-threshold`/`--extend-threshold`
(defaults 0.45/0.22). `--record-outcome` appends to
`registry/telemetry/plan-check-events.jsonl` (schema shared with
REUSE-WP-0019's reuse telemetry). `--file-request` files a State Hub
capability request on a `new` verdict (requires the hub reachable at
`127.0.0.1:8000`; degrades gracefully offline).
When `LLM_CONNECT_URL` is set, an optional rerank pass sends the top
deterministic candidates to llm-connect for a semantic confidence score.
Per design, this **never reorders or replaces** the deterministic result —
LLM-scored entries are appended after as separately-labeled `[llm]` matches,
so the trusted base result is identical whether or not the rerank runs.
Malformed/non-JSON LLM responses are rejected and reported as a note, never
silently guessed at; missing `LLM_CONNECT_URL` degrades the same way.
### catalog
Generate human-readable catalog artifacts (UC-RS-018).
@@ -110,8 +143,14 @@ Run the service locally: `REUSE_SURFACE_TOKEN=dev-token reuse-surface serve`
reuse-surface report gaps
reuse-surface report gaps --format json
reuse-surface report gaps --roster registry/federation/local-repo-roster.yaml
reuse-surface report gaps --check-capability-requests
```
`--check-capability-requests` (REUSE-WP-0018-T04) also lists open State Hub
capability requests with no matching federated capability; requires the hub
reachable at `127.0.0.1:8000` and degrades gracefully (prints "skipped") when
it isn't. Off by default so `report gaps` stays fast and offline-safe.
Workstation roster report: publish blockers, empty scaffolds, seed-ready repos,
and local index owner stubs pending dedup.
@@ -134,7 +173,7 @@ Bootstrap or discover a capability registry in the current or target repo.
reuse-surface establish --scaffold --domain helix_forge
reuse-surface establish --scaffold --path ../state-hub
reuse-surface establish --publish-check --raw-url https://.../capabilities.yaml
export LLM_CONNECT_URL=http://127.0.0.1:8088
export LLM_CONNECT_URL=http://127.0.0.1:8080
reuse-surface establish --discover --dry-run
reuse-surface establish --discover --apply
```
@@ -161,7 +200,7 @@ Interactive or automated registry maintenance (REUSE-WP-0016). Preferred entry
point for sibling repo operators.
```bash
export LLM_CONNECT_URL=http://127.0.0.1:8088 # optional
export LLM_CONNECT_URL=http://127.0.0.1:8080 # optional
reuse-surface maintain --all --from-git-since origin/main
reuse-surface maintain --capability capability.registry.register
reuse-surface maintain --all --auto --no-llm

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0001
type: workplan
title: "Bootstrap State Hub integration"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0002
type: workplan
title: "MVP registry foundation"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0003
type: workplan
title: "Close intent-scope gaps: docs, tooling, and registry growth"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0004
type: workplan
title: "Registry hardening: CI, overlap detection, and catalog"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0005
type: workplan
title: "Registry federation and relation graphs"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0006
type: workplan
title: "Registry hygiene and coverage expansion"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0007
type: workplan
title: "Interactive capability catalog"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0008
type: workplan
title: "Interactive relation graph explorer"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0009
type: workplan
title: "CLI hardening and test suite"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0010
type: workplan
title: "Network federation for remote indexes"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: REUSE-WP-0016
type: workplan
title: "Interactive registry maintain with llm-connect automation"
domain: helix_forge
domain: infotech
repo: reuse-surface
status: finished
owner: codex

View File

@@ -0,0 +1,287 @@
---
id: REUSE-WP-0017
type: workplan
title: "Capability coverage campaign: seed or explicitly close all empty scaffolds"
domain: infotech
repo: reuse-surface
status: finished
owner: claude-code
topic_slug: helix-forge
created: "2026-07-06"
updated: "2026-07-07"
state_hub_workstream_id: "a2d83504-fcd0-4561-8688-b77a01cb7f06"
---
# Capability coverage campaign: seed or explicitly close all empty scaffolds
The federation infrastructure is complete (61/61 repos established and
hub-registered, WP-0014/0015), but **51 of 61 roster repos publish zero
capabilities**. The federated index carries 24 capabilities, concentrated in a
handful of custodian-core repos. A registry that answers most queries with
"nothing found" trains agents to stop querying — content coverage is the
prerequisite for the consumption loop (REUSE-WP-0018) and the automation loop
(REUSE-WP-0019).
**Goal:** every roster repo either publishes ≥1 capability entry or carries an
explicit, machine-readable `no-reusable-capability` marker with a rationale.
"Empty" becomes informative instead of ambiguous.
**Baseline:** 10/61 repos with ≥1 capability; 51 empty scaffolds; 7 seed-ready
(gap report 2026-07-06).
**Target:** 0 ambiguous scaffolds; coverage ratio surfaced in `report gaps`
and CI.
## Approach
1. **Classify before drafting.** Sweep the 51 empty scaffolds and bucket each:
`has-capability` (draft entries) vs `no-capability` (mark explicitly —
experiments, forks, canon/docs-only repos, probes).
2. **Draft with existing tooling.** `reuse-surface establish --discover` +
llm-connect per repo produces first-pass entries at honest low maturity
(typically D1D2 / A0A1); no invented evidence.
3. **Human review gate.** Drafts land as branches/commits flagged for review;
nothing publishes to the federated index without a human pass (same pattern
as CUST-WP-0050 repo classification).
4. **Publish + recompose.** After review: publish-check, hub state refresh,
`federation compose`, catalog/graph regeneration.
## Dependencies
| Dependency | Owner | Notes |
|---|---|---|
| llm-connect | llm-connect | drafting backend for `establish --discover` |
| local-repo-roster.yaml | reuse-surface | 61-repo roster, sweep source of truth |
| .repo-classification.yaml | the-custodian | classification signals for the no-capability bucket |
| config-atlas publish fix | config-atlas | 303 publish blocker (T06) |
| Sibling repo write access | Bernd | drafts commit into sibling checkouts at `~/<slug>/` |
## Design decisions
- **`no-capability` marker format:** a `registry/NO_CAPABILITIES.md` file with
frontmatter (`reason`, `reviewed`, `revisit`) in the sibling repo, plus a
`capability_status: none` field in the roster row. `report gaps` treats
marked repos as resolved, not empty.
- **Honest maturity floor:** first-pass entries never claim above D2/A2/C2/R1
without cited on-disk evidence (WP-0016 evidence gates apply).
- **Batch execution:** repos are processed in cohorts of ~10 via
`/ralph-workplan`; each cohort ends with validate + gap-report delta.
---
## Extend Gap Report And Roster For Explicit No-Capability Status
```task
id: REUSE-WP-0017-T01
status: done
priority: high
state_hub_task_id: "90fe5bf2-7c03-4af5-bc4a-e5d608c7e879"
```
- Add `capability_status: has | none | pending` to roster schema and
`local-repo-roster.yaml` handling (default `pending` for empty scaffolds)
- `report gaps`: split "Empty scaffolds" into "Unclassified" and
"Explicitly none (n)"; add a coverage ratio headline
(`repos with capabilities or explicit none / total`)
- Define `registry/NO_CAPABILITIES.md` template in `templates/`
- Pytest coverage for both report paths
## Classification Sweep Of The 51 Empty Scaffolds
```task
id: REUSE-WP-0017-T02
status: done
priority: high
state_hub_task_id: "17ea8783-1344-4d04-8f7f-f93859ae4a20"
```
- For each empty-scaffold repo: read INTENT/SCOPE/README + top-level layout,
bucket `has-capability` vs `no-capability` with a one-line rationale
- Output: `history/2026-07-06-coverage-classification.md` table (repo, bucket,
rationale, candidate capability ids for the has bucket) — done, 37
has-capability / 13 no-capability / 0 missing
- Cross-check against `.repo-classification.yaml` where available
- **Human review checkpoint:** Bernd confirms the bucket split before T03/T04
## Mark No-Capability Repos
```task
id: REUSE-WP-0017-T03
status: done
priority: medium
state_hub_task_id: "3465cebf-84f9-4d67-8d8a-cca9771f2f06"
```
For each confirmed `no-capability` repo:
- Write `registry/NO_CAPABILITIES.md` from template into the sibling checkout — done, 13/13
- Set `capability_status: none` in the roster — done
- Commit in sibling repo with a uniform message referencing this workplan — done,
13/13 on origin (confirmed at T05 closeout 2026-07-07)
## Draft Capability Entries For Has-Capability Repos
```task
id: REUSE-WP-0017-T04
status: done
priority: high
state_hub_task_id: "20d67516-331b-46bd-8a90-1170139313c2"
```
**Note:** `establish --discover` requires a running `llm-connect` backend,
which isn't up on this workstation; spinning up a new inference service was
out of scope for this task. Entries are instead drafted directly by the
implementing agent, grounded in each repo's actual README/docs/tests (no
invented evidence), which better matches the "no invented evidence" design
principle anyway.
**Cohort 1 (10/37 done):** artifact-store, can-you-assist, citation-engine,
citation-evidence, email-connect, guide-board, hub-core, info-tech-canon,
infospace-bench, inter-hub. All validate; no overlap with the existing 24
federated capabilities. Committed locally in each sibling repo (push held
for T05). Coverage 24/61 -> 34/61.
**Cohort 2 (20/37 done):** issue-core (migrated its existing
`CAPABILITY-issue-tracking.yaml` into the standard registry location rather
than drafting fresh), kaizen-agentic, key-cape, kontextual-engine,
llm-connect, markitect-filter, markitect-main (registered honestly as a
superseded legacy platform pointing at its three successor repos, not a
forward-looking target), markitect-quarkdown, markitect-tool, net-kingdom.
All validate; no overlap with existing federated capabilities. Committed
locally (push held for T05). Coverage 34/61 -> 44/61. Also fixed a stale
`empty_scaffold_count >= 40` test threshold in `tests/test_reports.py`
no longer meaningful once the coverage campaign started shrinking that
number by design.
**Cohort 3 / final (17/17 done):** open-cmis-tck, open-reuse, ops-bridge,
phase-memory, railiance-apps, railiance-cluster, railiance-enablement,
railiance-fabric, railiance-forge, railiance-infra, railiance-platform,
repo-scoping, the-custodian, user-engine, vantage-point, vergabe-teilnahme,
whynot-design. All validate; no overlap with existing federated
capabilities. Committed locally (push held for T05). Coverage 44/61 ->
**61/61 — full coverage, T04 complete**.
Notable calls in this cohort:
- **the-custodian**: repo root is confidential/proprietary (NDA notice in
its README). Entry is deliberately scoped only to the non-confidential
`runtime/` agent framework and `tools/` repo-classification scripts;
canon/memory content is explicitly excluded from the entry's `includes`
and called out in `consumer_guidance.not_recommended_for`.
- **vergabe-teilnahme**: its own `SCOPE.md` is an unfilled template, so
discovery is honestly D1/C0 — flagged in `known_limitations` that filling
in SCOPE.md is the natural next step before further promotion.
- **markitect-main** (cohort 2) and **vantage-point**/**railiance-forge**
patterns repeat here: several repos (open-reuse, vantage-point) have a
stale repo-seed-template README; SCOPE.md/INTENT.md were used as the
authoritative source instead, noted per-entry.
- **railiance-enablement**, **railiance-apps**, **the-custodian**: each
surfaced tooling directly relevant to REUSE-WP-0019 (Forgejo promotion
script, forgejo-smoke.sh, patch-forgejo-remote-urls.sh) — worth cross-
referencing when that workplan starts its host-migration inventory (T01).
- `reuse-surface establish --discover` with llm-connect per repo
- Manual tightening: id namespace (`capability.<domain>.<verb>`), scope
boundary, honest vectors, relations to existing federated capabilities
- `reuse-surface validate` + `overlaps` check per cohort (no duplicate
semantics vs the existing 24)
- Drafts committed in sibling repos, flagged for human review; roster
`capability_status: has`
## Human Review And Publish Pass
```task
id: REUSE-WP-0017-T05
status: done
priority: high
state_hub_task_id: "56b68b6f-4dca-4fed-925f-d20783dc40d9"
```
- Bernd reviews drafted entries per cohort (maturity honesty, scope, ids)
- After approval: sibling repos push; `establish --publish-check` per repo;
hub registrations refreshed where raw URLs changed
- `reuse-surface federation compose` + catalog + graph regeneration in this repo
**Done 2026-07-07.** Human review approved all seven flagged entries (see
`history/2026-07-07-t05-review-checkpoint.md`). Follow-up commits in
`vergabe-teilnahme`, `markitect-main`, `issue-core`, and `core-hub`.
Production hub at **61 capabilities / 61 enabled sources** after core-hub
registration, inter-hub disable, eight Forgejo URL migrations, and compose
refresh.
## Fix config-atlas Publish Blocker
```task
id: REUSE-WP-0017-T06
status: done
priority: medium
state_hub_task_id: "1a04b60a-b982-4b5f-ae20-955bc88d4204"
```
**Diagnosis complete — two separate issues, conflated in the original note:**
1. **The 303 itself was not actually a live blocker.** Gitea's raw-URL
scheme is `/raw/main/<path>` -> 303 redirect -> `/raw/branch/main/<path>`
-> 200. `establish.py`'s `_probe_raw_url` uses `urllib.request.urlopen`,
which already follows redirects transparently (verified: HEAD request to
the `/raw/main/...` URL resolves to 200 today). Re-running
`establish --publish-check` for config-atlas now returns **PASS**. The
roster's `publish_check: fail` was stale data from the 2026-06-16 sweep,
not a reproducible current failure — corrected in the roster (publish
pass now 61/61).
2. **config-atlas was never actually registered with the production hub.**
Diffing `https://reuse.coulomb.social/v1/federated` (60 sources) against
the local roster (61 repos) shows config-atlas is the only mismatch —
present in `sources.yaml` and locally claimed `hub_registered: true`
(from the config-atlas registration commit), but genuinely absent from
the hub. Corrected the roster's `hub_registered` to `false` for
config-atlas (summary count 61 -> 60) to reflect reality.
**Resolved 2026-07-07.** A routing catalog entry
(`reuse-surface-hub-write-token`) appeared in
`~/ops-warden/registry/routing/catalog.yaml` pointing at the token's actual
home: a Kubernetes Secret (`reuse-surface-env` in namespace `reuse` on the
Railiance01 cluster), not an OpenBao/Vault lane. Registration was completed
through that channel (by a separate concurrent session, not this one — this
session verified the *result*, not the credential itself, per the
credential-routing rule against exploring live cluster secrets directly).
config-atlas is now present among the 61 sources at
`https://reuse.coulomb.social/v1/federated`. Roster corrected:
`hub_registered: true`, publish_sweep 2026-07-07.
- [x] Diagnose the 303 (resolved — not a current blocker, code already follows the redirect)
- [x] Local publish-check target: 61/61 pass
- [x] Complete hub registration for config-atlas — done 2026-07-07, 61/61 hub-registered
## Closeout: Coverage Metrics, Docs, SCOPE Update
```task
id: REUSE-WP-0017-T07
status: done
priority: low
state_hub_task_id: "24ac37bf-2509-4077-9aa3-c09a599dd644"
```
- `report gaps` shows 0 unclassified scaffolds; record final coverage ratio
- Update `SCOPE.md` Current State (capability count, coverage), regenerate
`docs/CapabilityCatalog.md`, graph, search catalog
- `history/` milestone note; progress event + fix-consistency
**Done 2026-07-07.** Coverage **62/62** roster repos; federated index **61**
capabilities on production hub; `history/2026-07-07-wp0017-coverage-campaign-complete.md`.
---
## Acceptance
- [x] Every roster repo is `has` or `none` — zero `pending` (62/62)
- [x] All `none` repos carry a reviewed `registry/NO_CAPABILITIES.md` (13)
- [x] Federated index capability count reflects the seeded entries (61 live; inter-hub disabled on hub)
- [x] 62/62 publish pass
- [x] Coverage ratio visible in `report gaps` and CI output
- [x] No entry claims maturity without on-disk evidence citations (T05 human review)
## Out of scope
- Consumption/plan-check tooling (REUSE-WP-0018)
- Forgejo webhooks/automation (REUSE-WP-0019)
- Maturity promotions beyond honest first-pass levels
- Multi-domain federation (all entries remain `helix_forge`)

View File

@@ -0,0 +1,257 @@
---
id: REUSE-WP-0018
type: workplan
title: "plan-check: close the consumption loop for capability reuse"
domain: infotech
repo: reuse-surface
status: finished
owner: claude-code
topic_slug: helix-forge
created: "2026-07-06"
updated: "2026-07-07"
state_hub_workstream_id: "cd8683ff-6e6c-4f6c-a62f-565bd55113ea"
reuse_check: "new — dogfooded 2026-07-07 via reuse-surface plan-check against the full 61-capability federated index; no existing capability covers query-before-build matching"
---
# plan-check: close the consumption loop for capability reuse
The registry is write-mostly: nothing nudges an agent planning work in a
sibling repo to query the federated index before building. The State Hub has a
`request_capability` / `list_capability_requests` flow, but it is not bridged
to reuse-surface data. This workplan adds the **query-before-build** step to
the ecosystem: a `plan-check` command that matches a draft workplan or intent
text against federated capabilities, wired into every repo's session protocol,
with a two-way bridge to State Hub capability requests.
This is the highest-leverage coherence mechanism: it is what stops 61 repos
from independently reinventing the same thing.
**Depends on REUSE-WP-0017** for meaningful match rates — plan-check against a
mostly-empty index produces noise and erodes trust. T01T03 (design +
implementation) can proceed in parallel with the coverage campaign; the
ecosystem rollout (T05) waits for coverage.
## Proposed CLI surface
```bash
# Match a draft workplan against the federated index
reuse-surface plan-check workplans/XXX-WP-0042-something.md
# Free-text intent
reuse-surface plan-check --intent "parse invoices from email and file evidence"
# Agent-consumable output
reuse-surface plan-check --format json workplans/XXX-WP-0042.md
# Record the outcome (feeds R-axis evidence, WP-0019)
reuse-surface plan-check ... --record-outcome reused|extended|new
```
**Verdict classes per match:** `reuse` (capability covers the need — link it),
`extend` (close scope — extend instead of duplicating), `new` (no match —
proceed, optionally file a capability request). Output includes capability id,
maturity vector, owning repo, and consumer guidance.
## Design principles
1. **Deterministic core, LLM assist optional** — keyword/scope/relation
matching against index rows works without llm-connect; semantic matching
via llm-connect improves recall when available (same optional-backend
pattern as WP-0016).
2. **Advisory, not blocking** — plan-check informs; it never vetoes a
workplan. Adoption comes from usefulness plus protocol convention, not
gates.
3. **One line in the session protocol** — sibling integration is a single
instruction in the shared `.claude/rules/` template: run plan-check before
creating a workplan. No per-repo bespoke wiring.
4. **Every check is a data point** — plan-check invocations and recorded
outcomes are the raw material for reuse telemetry (WP-0019-T04).
## Dependencies
| Dependency | Owner | Notes |
|---|---|---|
| REUSE-WP-0017 coverage | reuse-surface | rollout gate for T05 |
| federated.yaml freshness | reuse-surface | improved by WP-0019 auto-recompose |
| llm-connect | llm-connect | optional semantic matching backend |
| State Hub capability-request API | state-hub | `request_capability`, `list_capability_requests` |
| .claude/rules template propagation | the-custodian / repo-seed | mechanism for T05 rollout |
---
## Design Matching Semantics And Verdict Model
```task
id: REUSE-WP-0018-T01
status: done
priority: high
state_hub_task_id: "c7b3407a-2b9d-46ba-ae9c-7ab12235f0ba"
```
Design doc `specs/PlanCheck.md`:
- Input model: workplan file (frontmatter + body) vs free intent text
- Match pipeline: token/keyword scoring over id, name, scope, guidance →
relation expansion → optional llm-connect semantic rerank
- Verdict thresholds and tie-breaking (maturity vector as rank signal:
prefer higher D/A when scopes tie)
- JSON output schema (`schemas/plan-check-result.schema.json`)
- Outcome-recording format (append-only JSONL under `registry/telemetry/`,
schema shared with WP-0019-T04)
## Implement Deterministic plan-check
```task
id: REUSE-WP-0018-T02
status: done
priority: high
state_hub_task_id: "24911d0e-41bc-4c8c-a39a-7dc8ec65d8c6"
```
Implemented in `reuse_surface/plan_check.py` + `plan-check` CLI command:
- Reuses `overlaps.py`'s `TOKEN_RE`/Jaccard approach rather than a second
scoring method (per spec §4)
- Reads `registry/indexes/federated.yaml`, falls back to the local index,
warns when the composed index is >14 days stale
- Markdown (TTY default) and `--format json` (validated against
`schemas/plan-check-result.schema.json`)
- 11 pytest cases: verdict thresholds, workplan-file parsing, intent-text
parsing, tie-window vector ranking, staleness warning, CLI wiring
- `--record-outcome` appends to `registry/telemetry/plan-check-events.jsonl`
per the shared `schemas/reuse-event.schema.json` (WP-0019-T04 will post the
same shape to the hub)
## Add llm-connect Semantic Rerank
```task
id: REUSE-WP-0018-T03
status: done
priority: medium
state_hub_task_id: "9040505a-238e-4815-ae1a-8c5f8c12afa9"
```
Unblocked 2026-07-07 — `llm-connect` running locally (mock provider,
`127.0.0.1:8080`). Implemented in `reuse_surface/plan_check.py`:
- `build_rerank_prompt`/`request_rerank`/`apply_rerank`, reusing
`llm_bridge.execute_prompt`/`extract_json_object` (same pattern as
`maintain_llm.py`'s `request_maintain_patches`)
- New schema `schemas/plan-check-rerank.schema.json`; malformed responses
(missing fields, non-JSON, invented candidate ids not in the input set)
are rejected via schema validation + an id-membership filter, never
guessed at
- `apply_rerank` **appends** LLM-scored entries after the deterministic
list (`kind: "llm"`) rather than reordering it — deterministic matches
keep their original order/scores untouched, per design principle 3
- Graceful skip (`ValueError` → a `notes` field in the result, mirroring
`maintain.py`'s `no_llm`/skip pattern) when `LLM_CONNECT_URL` is unset
or the LLM response is malformed; `--no-llm` skips without attempting a
call at all
- New CLI flags: `--llm-url`, `--no-llm`
- `schemas/plan-check-result.schema.json` extended for `notes` and the
(pre-existing, previously unschema'd) `filed_capability_request` field
- 9 new pytest cases (valid, malformed, non-JSON, invented-id-filtering,
append-not-reorder, graceful skip, `--no-llm` short-circuit, successful
integration) — 89 total pass
- **Live-verified**, not just mocked: real HTTP round-trip against the
running mock `llm-connect` instance correctly rejected its non-JSON mock
response and surfaced the skip note, while the deterministic verdict and
match order stayed completely unaffected
## Bridge State Hub Capability Requests
```task
id: REUSE-WP-0018-T04
status: done
priority: medium
state_hub_task_id: "8249dca7-18ca-4150-9671-ca87e6a5ad88"
```
Implemented in `reuse_surface/statehub_bridge.py` against the live local
State Hub HTTP API (`http://127.0.0.1:8000`, confirmed reachable):
- **new → request:** `plan-check --file-request` files a State Hub
capability request (`POST /capability-requests/`) on a `new` verdict
- **request → gap:** `report gaps --check-capability-requests` lists open
requests (`GET /capability-requests/`, filtered on `status` — a request
can carry a `catalog_entry_id` while still `requested`/routed, so status
is the correct open/closed signal, not `catalog_entry_id` presence).
Opt-in flag so `report gaps` stays fast and offline-safe by default
- Both degrade to `None`/graceful-skip when the hub is unreachable; neither
ever blocks the primary command
- List endpoint observed taking ~7s in production with only 5 rows; uses a
20s timeout distinct from the 5s health-check timeout
- 5 pytest cases with the hub client fully mocked (never hits the network
in the default test run)
- **Not done:** marking matched requests resolved with a pointer to the
capability id — deferred, no current caller needs it yet
## Ecosystem Rollout: Session-Protocol Integration
```task
id: REUSE-WP-0018-T05
status: done
priority: high
state_hub_task_id: "c232a669-b263-4978-a8b1-3721b8bfaa68"
```
Gate cleared 2026-07-07: WP-0017 finished, `federated.yaml` composed with
61 real capabilities (up from the original 24). Dogfooded immediately —
`plan-check` against "unified interface for issue tracking across Gitea
GitHub GitLab" now correctly returns `extend` pointing at
`capability.infotech.issue-tracking`, where before WP-0017 published it
would have returned `new`. That's the whole point of sequencing T05 after
WP-0017 landed.
- [x] Drafted the convention in `.claude/rules/workplan-convention.md`
("Query before building" section): run `plan-check`, record
`reuse_check: reuse|extend|new — <capability-id?>` in frontmatter,
advisory only
- [x] Sent State Hub message to `custodian-agent` proposing propagation
via the fix-consistency/repo-seed shared rules template (message
`13cecd08-14e8-46d9-880c-65b8fef857cb`) — propagation itself depends on
the-custodian's own template mechanism, not something reuse-surface
can push directly into sibling repos
- [x] Added `reuse_check` frontmatter to this repo's own REUSE-WP-0018 and
REUSE-WP-0019 as the reference implementation, both dogfooded for real
(both correctly verdict `new`)
## Docs, CI, SCOPE
```task
id: REUSE-WP-0018-T06
status: done
priority: low
state_hub_task_id: "ff0a91eb-cc41-487b-b726-8c2f84860399"
```
- `tools/README.md` command reference for `plan-check` and
`report gaps --check-capability-requests`; `docs/RegistryFederation.md`
"Query before building" consumer section; `SCOPE.md` "What Is Possible Now"
- CI: informational `plan-check --intent "smoke test" --format json` run
added to `.gitea/workflows/ci.yml`
- `docs/IntentScopeGapAnalysis.md`: priority 29 added, marked **Partial**
(T02/T04 shipped; T03/T05 remain open — not flipped fully to "shipped"
since the ecosystem rollout hasn't happened yet)
---
## Acceptance
- [x] `plan-check` returns reuse/extend/new verdicts for workplan files and intent text, with and without llm-connect (T03 shipped 2026-07-07, live-verified against a running instance)
- [x] JSON output validates against the published schema
- [x] `new` verdicts can file State Hub capability requests; `report gaps` lists unmatched open requests
- [x] Session-protocol convention drafted; propagation *proposed* to custodian-agent
2026-07-07 (message `13cecd08-14e8-46d9-880c-65b8fef857cb`) — actual
adoption into the shared rules template is the-custodian's call, not
something reuse-surface can complete unilaterally
- [x] reuse-surface itself records `reuse_check` in its own workplans
(REUSE-WP-0018, REUSE-WP-0019) as the reference implementation
## Out of scope
- Blocking/gating workplan creation on plan-check results
- Reuse telemetry aggregation and R-axis evidence (WP-0019)
- Embedding-based matching (llm-connect rerank only)
- Editing sibling repos' rules files directly (owned by template propagation)

View File

@@ -0,0 +1,276 @@
---
id: REUSE-WP-0019
type: workplan
title: "Forgejo-native federation automation and reuse telemetry"
domain: infotech
repo: reuse-surface
status: active
owner: claude-code
topic_slug: helix-forge
created: "2026-07-06"
updated: "2026-07-07"
state_hub_workstream_id: "569be717-34f8-4039-bb26-497685f60159"
reuse_check: "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
1. **Host-agnostic first** — a single `forge_base_url` configuration
(env/config + hub setting) replaces hardcoded hosts; the Forgejo cutover
becomes a one-line change per surface.
2. **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.
3. **Degrade to schedule** — if webhooks are unavailable, a scheduled Forgejo
Actions job recomposes on an interval; freshness is monitored either way.
4. **Telemetry is append-only and low-ceremony** — reuse events are JSONL
facts (who consumed what, when, outcome); aggregation derives `reused_by`
relations 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
```task
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` (reads
`REUSE_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 on `activity-core`/`state-hub`
- Fixed two host-agnostic code gaps found while inventorying:
`registry_update.py`'s `SAFE_EVIDENCE_PREFIXES` and `maintain_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 (`8088` vs the real llm-connect
default `8080`) in `tools/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.yaml` post-migration: still 61 capabilities, no
loss
## Hub Recompose Endpoint And Webhook Receiver
```task
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_state` table (`composed_at`, `stale`), with
`record_compose()`/`mark_stale()`/`get_compose_state()`. `composed_at`
updates and `stale` clears only on a *forced* recompose (`refresh=true`,
webhook, or future scheduled fallback) — a plain `GET` still serves
current best-effort data but never silently reports itself as freshly
composed
- `webhooks.py`: `verify_signature` (constant-time HMAC-SHA256, fails
closed on empty secret), `push_touches_registry_index` (path-only
inspection of the push payload's `added`/`modified`/`removed` lists —
never parses file content, per design principle 2)
- `app.py`: `POST /v1/webhooks/forgejo` (accepts both `X-Forgejo-Signature`
and `X-Gitea-Signature`, since repos migrate independently);
`GET /v1/federated` and `POST /v1/federated/compose` now share an
`asyncio.Lock` so concurrent recompose triggers (manual, webhook, future
scheduled) coalesce instead of overlapping
- `specs/FederationHubAPI.md` extended (§5.7-5.9, config table, error
codes)
- 28 new pytest cases (16 in `test_hub.py`, 12 in `test_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 — confirmed `composed_at`/`stale` transitions, 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
```task
id: REUSE-WP-0019-T03
status: wait
priority: medium
state_hub_task_id: "aa9e9f80-b878-490c-832e-515d8cbbbb60"
```
Blocked on T02 deploy and Forgejo instance availability.
- Org-level Forgejo webhook (single config, all repos) → hub
`/v1/webhooks/forgejo`, push events only
- Fallback: Forgejo Actions scheduled workflow (cron) in this repo calling
`POST /v1/recompose`; also serves repos during any Gitea-remnant window
- Migrate this repo's CI `.gitea/workflows/ci.yml` → `.forgejo/workflows/ci.yml`
(Forgejo Actions; verify runner labels); document the pattern for siblings
- Verify end-to-end: index change in a sibling repo → hub `composed_at`
advances without manual compose
## Reuse Telemetry Store And Recording
```task
id: REUSE-WP-0019-T04
status: todo
priority: medium
state_hub_task_id: "c8e9064e-5c39-4c84-80e9-8b255f8edaec"
```
- Implement the shared schema from WP-0018-T01: reuse events
`{ts, consumer_repo, capability_id, verdict, outcome?, source: plan-check|manual|hub}`
- Hub: `POST /v1/reuse-events` (token-auth) + local JSONL fallback when hub
unreachable; `GET /v1/reuse-events?capability_id=` for aggregation
- `plan-check --record-outcome` (WP-0018) posts here; manual
`reuse-surface record-reuse` for retroactive facts
- Privacy/scope: repo slugs and capability ids only — no code, no secrets
## Telemetry Aggregation Into R-Axis Evidence
```task
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; feeds `reused_by` relation 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.md` amendment)
- Catalog + graph surface consumer counts
## Freshness Monitoring, Docs, SCOPE
```task
id: REUSE-WP-0019-T06
status: todo
priority: low
state_hub_task_id: "a9f44d45-91e2-4b43-909f-30a5f906cf3b"
```
- `reuse-surface stats`: hub `composed_at` age + stale flag; CI informational
check warns when the hub index is older than N days
- `docs/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-host` tested
- [ ] Push to a sibling repo's `registry/indexes/` recomposes the hub index without manual action (webhook), with scheduled fallback in place
- [ ] This repo's CI runs on Forgejo Actions (`.forgejo/workflows/`)
- [ ] Reuse events recordable via hub API and CLI; `report reuse` aggregates them
- [ ] R-axis evidence rules for observed reuse documented in the maturity standard
- [ ] Hub freshness visible (`composed_at`, stale flag) in API and stats
## 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)