generated from coulomb/repo-seed
Compare commits
42 Commits
fe05d60a66
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ac81490b87 | |||
| 9602b431ba | |||
|
|
e3ae22e35b | ||
| e0a4de3310 | |||
|
|
443510276b | ||
| 00b7eab154 | |||
|
|
5aaa4c31c9 | ||
| 0c76ccb28b | |||
|
|
a1b3425c84 | ||
| af93b2a91c | |||
|
|
0a435eaad3 | ||
| a5e9a4a242 | |||
| 89ca9b3891 | |||
| c97834447d | |||
| d3a5ec85f1 | |||
| 9b02e54bb6 | |||
|
|
cf3b8f93db | ||
| 533b4f0831 | |||
| e1ca9bbbc5 | |||
| e72966a658 | |||
| 2a6818d4fe | |||
| e046562b56 | |||
| 8fc975717f | |||
| 8726cb8c86 | |||
| 89b8b34471 | |||
| 651b84a16b | |||
| b48a4edf81 | |||
| 8429eea576 | |||
| 6688830bf8 | |||
| d3ae49fdf3 | |||
| be1dd56d76 | |||
| f4a5cefa4b | |||
| e87a07014c | |||
| a6ed45bb22 | |||
| f383f01dac | |||
| 8219a5d253 | |||
| 493f23123c | |||
| 64b57e69f0 | |||
| 365db676cf | |||
| e37fc60ecf | |||
| 08f3bb5110 | |||
| 368fb156d4 |
@@ -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.
|
||||
|
||||
@@ -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
162
AGENTS.md
@@ -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
|
||||
|
||||
27
CLAUDE.md
27
CLAUDE.md
@@ -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
|
||||
|
||||
32
SCOPE.md
32
SCOPE.md
@@ -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`).
|
||||
|
||||
@@ -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 (T01–T06) 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` (25–27)
|
||||
`workplans/REUSE-WP-0015-federation-polish-and-planning-analytics.md` (25–27);
|
||||
`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 25–27 proposed |
|
||||
| 2026-06-16 | Assessment persisted; **REUSE-WP-0015** created for priorities 25–27 |
|
||||
| 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 T01–T06 tasks shipped) |
|
||||
@@ -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
|
||||
|
||||
106
history/2026-07-06-coverage-classification.md
Normal file
106
history/2026-07-06-coverage-classification.md
Normal 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 A1–A2), 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.
|
||||
171
history/2026-07-06-t04-review-summary.md
Normal file
171
history/2026-07-06-t04-review-summary.md
Normal 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 (T02–T04):** 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
|
||||
1–2 — 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.
|
||||
65
history/2026-07-07-inter-hub-retirement.md
Normal file
65
history/2026-07-07-inter-hub-retirement.md
Normal 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 0–9 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.
|
||||
67
history/2026-07-07-t05-review-checkpoint.md
Normal file
67
history/2026-07-07-t05-review-checkpoint.md
Normal 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`)
|
||||
35
history/2026-07-07-wp0017-coverage-campaign-complete.md
Normal file
35
history/2026-07-07-wp0017-coverage-campaign-complete.md
Normal 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
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
105
reuse_surface/forge_host.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"])}
|
||||
37
reuse_surface/hub/webhooks.py
Normal file
37
reuse_surface/hub/webhooks.py
Normal 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
|
||||
@@ -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
392
reuse_surface/plan_check.py
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
103
reuse_surface/statehub_bridge.py
Normal file
103
reuse_surface/statehub_bridge.py
Normal 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]
|
||||
27
schemas/plan-check-rerank.schema.json
Normal file
27
schemas/plan-check-rerank.schema.json
Normal 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"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
schemas/plan-check-result.schema.json
Normal file
59
schemas/plan-check-result.schema.json
Normal 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"]}
|
||||
}
|
||||
}
|
||||
29
schemas/reuse-event.schema.json
Normal file
29
schemas/reuse-event.schema.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
170
specs/PlanCheck.md
Normal 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.22–0.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.
|
||||
24
templates/NO_CAPABILITIES.template.md
Normal file
24
templates/NO_CAPABILITIES.template.md
Normal 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
188
tests/test_forge_host.py
Normal 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
|
||||
@@ -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
351
tests/test_plan_check.py
Normal 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"
|
||||
@@ -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
|
||||
|
||||
|
||||
66
tests/test_statehub_bridge.py
Normal file
66
tests/test_statehub_bridge.py
Normal 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
|
||||
@@ -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
82
tests/test_webhooks.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
287
workplans/REUSE-WP-0017-capability-coverage-campaign.md
Normal file
287
workplans/REUSE-WP-0017-capability-coverage-campaign.md
Normal 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 D1–D2 / A0–A1); 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`)
|
||||
257
workplans/REUSE-WP-0018-plan-check-consumption-loop.md
Normal file
257
workplans/REUSE-WP-0018-plan-check-consumption-loop.md
Normal 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. T01–T03 (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)
|
||||
276
workplans/REUSE-WP-0019-forgejo-automation-and-telemetry.md
Normal file
276
workplans/REUSE-WP-0019-forgejo-automation-and-telemetry.md
Normal 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)
|
||||
Reference in New Issue
Block a user