REUSE-WP-0019-T05: reuse telemetry aggregation into R-axis evidence
Some checks failed
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
ci / validate-registry (push) Has been cancelled
Build and Publish Container Image / build-and-push (push) Successful in 1m23s

reuse_surface/reports.py: collect_reuse_events() merges the hub's
GET /v1/reuse-events (if reachable) with this repo's local JSONL fallback,
deduped. collect_reuse_report() aggregates per-capability consumer counts,
outcome breakdown, and last-used. collect_reused_by_suggestions() proposes
evidence-gated relation_add patches -- only for capabilities this repo
owns, only for consumer repos not already listed -- reusing the existing
patches.py:apply_patches mechanism (relation_add already isn't in
SAFE_DETERMINISTIC_KINDS, so it was already never auto-applied by
maintain --auto).

New CLI: reuse-surface report reuse [--capability-id] [--format]
[--suggest-relations] [--apply]. --apply requires --suggest-relations and
is the only thing that writes -- nothing happens automatically from
telemetry alone.

schemas/capability.schema.yaml: added relations.reused_by as a new
repoSlugList type, distinct from the existing capability-id relations,
since reused-by targets are consumer repo slugs.

specs/CapabilityMaturityStandard.md Sec8.9: what observed-reuse evidence
counts toward R2->R3 (single corroborating consumer) vs R3->R4+ (multiple
independent consumers) and what it never substitutes for.

19 new pytest cases, 162 total pass. Live-verified with synthetic local
events against a real capability entry: --suggest-relations --apply
correctly wrote relations.reused_by via the real apply_patches path
(reverted after, since it was a smoke test).

Deliberately deferred: surfacing consumer counts in the catalog/graph --
graph.py's relation model is capability-to-capability edges, a different
namespace than repo-slug reused_by targets; catalog.py doesn't currently
parse full front matter per entry. Left for a follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-07 22:47:51 +02:00
parent e6e275ce79
commit bca7165e02
8 changed files with 576 additions and 13 deletions

View file

@ -35,11 +35,16 @@ from reuse_surface.plan_check import (
from reuse_surface.reports import (
cohort_filters_from_args,
collect_gap_report,
collect_reuse_events,
collect_reuse_report,
collect_reused_by_suggestions,
default_roster_path,
format_cohort_json,
format_cohort_markdown,
format_gap_json,
format_gap_markdown,
format_reuse_report_json,
format_reuse_report_markdown,
select_cohort,
)
from reuse_surface.establish import (
@ -753,6 +758,46 @@ def cmd_report_gaps(args: argparse.Namespace) -> int:
return 0
def cmd_report_reuse(args: argparse.Namespace) -> int:
if args.apply and not args.suggest_relations:
print("error: --apply requires --suggest-relations", file=sys.stderr)
return 1
events = collect_reuse_events(hub_url=args.hub_url)
report = collect_reuse_report(events, capability_id=args.capability_id)
suggestions: list[dict[str, Any]] = []
if args.suggest_relations:
suggestions = collect_reused_by_suggestions(report)
applied: list[str] = []
if args.apply and suggestions:
from reuse_surface.patches import apply_patches
from reuse_surface.registry import ROOT
applied = apply_patches(ROOT, suggestions)
if args.format == "json":
payload = dict(report)
if args.suggest_relations:
payload["reused_by_suggestions"] = suggestions
if args.apply:
payload["applied"] = applied
print(format_reuse_report_json(payload))
else:
print(format_reuse_report_markdown(report), end="")
if args.suggest_relations:
print(f"\n## relations.reused_by suggestions ({len(suggestions)})\n")
if not suggestions:
print("_None — all observed reuse already reflected, or no capabilities owned here._")
else:
for s in suggestions:
print(f"- `{s['capability_id']}`: {s['detail']}")
if args.apply:
print(f"\nApplied {len(applied)} patch(es).")
return 0
def cmd_export(args: argparse.Namespace) -> int:
index = load_index()
bundle: dict[str, Any] = {
@ -1073,6 +1118,27 @@ def main(argv: list[str] | None = None) -> int:
)
gaps.set_defaults(func=cmd_report_gaps)
reuse = report_sub.add_parser(
"reuse",
help="reuse telemetry aggregation: consumer counts, outcomes, last-used (REUSE-WP-0019-T05)",
)
reuse.add_argument("--capability-id", help="filter to one capability")
reuse.add_argument("--hub-url", help="hub base URL (or REUSE_SURFACE_URL)")
reuse.add_argument("--format", choices=["markdown", "json"], default="markdown")
reuse.add_argument(
"--suggest-relations",
action="store_true",
help="also list evidence-gated relations.reused_by suggestions for "
"capabilities this repo owns",
)
reuse.add_argument(
"--apply",
action="store_true",
help="apply --suggest-relations suggestions (requires --suggest-relations; "
"never applies without this explicit flag)",
)
reuse.set_defaults(func=cmd_report_reuse)
stats = subparsers.add_parser("stats", help="registry maturity and federation stats")
stats.add_argument("--path", help="repo root (default: cwd)")
stats.add_argument(