REUSE-WP-0019-T05: reuse telemetry aggregation into R-axis evidence
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:
parent
e6e275ce79
commit
bca7165e02
8 changed files with 576 additions and 13 deletions
|
|
@ -5,12 +5,19 @@ import json
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from reuse_surface.reports import (
|
||||
cohort_filters_from_args,
|
||||
collect_gap_report,
|
||||
collect_reuse_events,
|
||||
collect_reuse_report,
|
||||
collect_reused_by_suggestions,
|
||||
format_cohort_json,
|
||||
format_cohort_markdown,
|
||||
format_gap_markdown,
|
||||
format_reuse_report_json,
|
||||
format_reuse_report_markdown,
|
||||
select_cohort,
|
||||
)
|
||||
|
||||
|
|
@ -189,4 +196,244 @@ def test_cmd_report_cohorts_markdown(monkeypatch):
|
|||
lambda: SAMPLE_INDEX,
|
||||
)
|
||||
exit_code = main(["report", "cohorts", "--planning-min", "D5"])
|
||||
assert exit_code == 0
|
||||
assert exit_code == 0
|
||||
|
||||
# --- T05: reuse telemetry aggregation ---
|
||||
|
||||
SAMPLE_REUSE_EVENTS = [
|
||||
{
|
||||
"ts": "2026-07-01T00:00:00Z",
|
||||
"consumer_repo": "repo-a",
|
||||
"capability_id": "capability.infotech.issue-tracking",
|
||||
"verdict": "reuse",
|
||||
"outcome": "reused",
|
||||
"source": "plan-check",
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-02T00:00:00Z",
|
||||
"consumer_repo": "repo-b",
|
||||
"capability_id": "capability.infotech.issue-tracking",
|
||||
"verdict": "reuse",
|
||||
"outcome": "reused",
|
||||
"source": "manual",
|
||||
},
|
||||
{
|
||||
"ts": "2026-07-03T00:00:00Z",
|
||||
"consumer_repo": "repo-a",
|
||||
"capability_id": "capability.audit.event-retain",
|
||||
"verdict": "extend",
|
||||
"outcome": "extended",
|
||||
"source": "plan-check",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_collect_reuse_report_aggregates_per_capability():
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
assert report["total_events"] == 3
|
||||
by_id = {row["capability_id"]: row for row in report["capabilities"]}
|
||||
assert by_id["capability.infotech.issue-tracking"]["consumer_count"] == 2
|
||||
assert by_id["capability.infotech.issue-tracking"]["consumer_repos"] == ["repo-a", "repo-b"]
|
||||
assert by_id["capability.infotech.issue-tracking"]["outcomes"] == {"reused": 2}
|
||||
assert by_id["capability.infotech.issue-tracking"]["last_used"] == "2026-07-02T00:00:00Z"
|
||||
assert by_id["capability.audit.event-retain"]["consumer_count"] == 1
|
||||
|
||||
|
||||
def test_collect_reuse_report_filters_by_capability_id():
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS, capability_id="capability.audit.event-retain")
|
||||
assert len(report["capabilities"]) == 1
|
||||
assert report["capabilities"][0]["capability_id"] == "capability.audit.event-retain"
|
||||
|
||||
|
||||
def test_collect_reuse_report_ignores_events_without_capability_id():
|
||||
events = SAMPLE_REUSE_EVENTS + [
|
||||
{"ts": "2026-07-04T00:00:00Z", "consumer_repo": "repo-c", "capability_id": None, "verdict": "new", "outcome": "new", "source": "manual"}
|
||||
]
|
||||
report = collect_reuse_report(events)
|
||||
assert report["total_events"] == 4
|
||||
assert len(report["capabilities"]) == 2
|
||||
|
||||
|
||||
def test_format_reuse_report_markdown_empty():
|
||||
output = format_reuse_report_markdown({"capabilities": [], "total_events": 0})
|
||||
assert "No reuse events recorded yet" in output
|
||||
|
||||
|
||||
def test_format_reuse_report_markdown_nonempty():
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
output = format_reuse_report_markdown(report)
|
||||
assert "capability.infotech.issue-tracking" in output
|
||||
assert "3" in output
|
||||
|
||||
|
||||
def test_format_reuse_report_json_roundtrips():
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
payload = json.loads(format_reuse_report_json(report))
|
||||
assert payload["total_events"] == 3
|
||||
|
||||
|
||||
def test_collect_reuse_events_merges_hub_and_local(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.hub_client.hub_list_reuse_events",
|
||||
lambda capability_id=None, base_url=None: (200, {"events": [SAMPLE_REUSE_EVENTS[0]]}),
|
||||
)
|
||||
telemetry_dir = tmp_path / "registry" / "telemetry"
|
||||
telemetry_dir.mkdir(parents=True)
|
||||
(telemetry_dir / "plan-check-events.jsonl").write_text(
|
||||
json.dumps(SAMPLE_REUSE_EVENTS[1], sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
events = collect_reuse_events(repo_root=tmp_path)
|
||||
assert len(events) == 2
|
||||
|
||||
|
||||
def test_collect_reuse_events_dedupes_identical_facts(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.hub_client.hub_list_reuse_events",
|
||||
lambda capability_id=None, base_url=None: (200, {"events": [SAMPLE_REUSE_EVENTS[0]]}),
|
||||
)
|
||||
telemetry_dir = tmp_path / "registry" / "telemetry"
|
||||
telemetry_dir.mkdir(parents=True)
|
||||
(telemetry_dir / "plan-check-events.jsonl").write_text(
|
||||
json.dumps(SAMPLE_REUSE_EVENTS[0], sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
events = collect_reuse_events(repo_root=tmp_path)
|
||||
assert len(events) == 1
|
||||
|
||||
|
||||
def test_collect_reuse_events_degrades_gracefully_when_hub_unreachable(tmp_path, monkeypatch):
|
||||
monkeypatch.delenv("REUSE_SURFACE_URL", raising=False)
|
||||
events = collect_reuse_events(repo_root=tmp_path)
|
||||
assert events == []
|
||||
|
||||
|
||||
def _write_entry_with_relations(tmp_path: Path, cap_id: str, reused_by: list[str]) -> str:
|
||||
rel = "registry/capabilities/capability-demo-sample.md"
|
||||
front_matter = {
|
||||
"id": cap_id,
|
||||
"name": "Sample",
|
||||
"summary": "Sample",
|
||||
"owner": "demo",
|
||||
"status": "draft",
|
||||
"domain": "helix_forge",
|
||||
"tags": ["demo"],
|
||||
"maturity": {
|
||||
"discovery": {"current": "D3", "target": "D5", "confidence": "low"},
|
||||
"availability": {"current": "A3", "target": "A4", "confidence": "low"},
|
||||
},
|
||||
"external_evidence": {
|
||||
"completeness": {"level": "C2", "confidence": "low"},
|
||||
"reliability": {"level": "R2", "confidence": "low"},
|
||||
},
|
||||
"discovery": {"intent": "demo", "includes": [], "excludes": []},
|
||||
"availability": {
|
||||
"current_level": "A3",
|
||||
"target_level": "A4",
|
||||
"current_artifacts": [],
|
||||
"consumption_modes": ["informational"],
|
||||
},
|
||||
"relations": {"depends_on": [], "supports": [], "related_to": [], "reused_by": reused_by},
|
||||
"evidence": {"documentation": [], "tests": []},
|
||||
"consumer_guidance": {
|
||||
"recommended_for": [],
|
||||
"not_recommended_for": [],
|
||||
"known_limitations": [],
|
||||
},
|
||||
}
|
||||
path = tmp_path / rel
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text("---\n" + yaml.safe_dump(front_matter, sort_keys=False) + "---\n", encoding="utf-8")
|
||||
return rel
|
||||
|
||||
|
||||
def test_collect_reused_by_suggestions_skips_already_listed_consumers(tmp_path):
|
||||
rel = _write_entry_with_relations(tmp_path, "capability.infotech.issue-tracking", reused_by=["repo-a"])
|
||||
index_path = tmp_path / "registry" / "indexes" / "capabilities.yaml"
|
||||
index_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
index_path.write_text(
|
||||
yaml.safe_dump({
|
||||
"version": 1, "domain": "helix_forge", "updated": "2026-07-01",
|
||||
"capabilities": [{"id": "capability.infotech.issue-tracking", "path": rel, "vector": "D3/A3/C2/R2"}],
|
||||
}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
suggestions = collect_reused_by_suggestions(report, repo_root=tmp_path)
|
||||
# repo-a already listed -> only repo-b should be suggested
|
||||
assert len(suggestions) == 1
|
||||
assert suggestions[0]["value"] == {"type": "reused_by", "target": "repo-b"}
|
||||
assert suggestions[0]["kind"] == "relation_add"
|
||||
|
||||
|
||||
def test_collect_reused_by_suggestions_skips_capabilities_not_owned_locally(tmp_path):
|
||||
# empty local index -- no capabilities owned here at all
|
||||
index_path = tmp_path / "registry" / "indexes" / "capabilities.yaml"
|
||||
index_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
index_path.write_text(
|
||||
yaml.safe_dump({"version": 1, "domain": "helix_forge", "updated": "2026-07-01", "capabilities": []}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
suggestions = collect_reused_by_suggestions(report, repo_root=tmp_path)
|
||||
assert suggestions == []
|
||||
|
||||
|
||||
def test_collect_reused_by_suggestions_no_index_returns_empty(tmp_path):
|
||||
report = collect_reuse_report(SAMPLE_REUSE_EVENTS)
|
||||
suggestions = collect_reused_by_suggestions(report, repo_root=tmp_path)
|
||||
assert suggestions == []
|
||||
|
||||
|
||||
def test_cmd_report_reuse_markdown(monkeypatch):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reuse_events", lambda hub_url=None: SAMPLE_REUSE_EVENTS)
|
||||
exit_code = main(["report", "reuse"])
|
||||
assert exit_code == 0
|
||||
|
||||
|
||||
def test_cmd_report_reuse_json(monkeypatch, capsys):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reuse_events", lambda hub_url=None: SAMPLE_REUSE_EVENTS)
|
||||
exit_code = main(["report", "reuse", "--format", "json"])
|
||||
assert exit_code == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["total_events"] == 3
|
||||
|
||||
|
||||
def test_cmd_report_reuse_apply_without_suggest_relations_errors(monkeypatch):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reuse_events", lambda hub_url=None: [])
|
||||
exit_code = main(["report", "reuse", "--apply"])
|
||||
assert exit_code == 1
|
||||
|
||||
|
||||
def test_cmd_report_reuse_suggest_relations(monkeypatch, capsys):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reuse_events", lambda hub_url=None: SAMPLE_REUSE_EVENTS)
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.cli.collect_reused_by_suggestions",
|
||||
lambda report: [{"capability_id": "capability.infotech.issue-tracking", "kind": "relation_add", "detail": "observed reuse by repo-a", "value": {"type": "reused_by", "target": "repo-a"}}],
|
||||
)
|
||||
exit_code = main(["report", "reuse", "--suggest-relations"])
|
||||
assert exit_code == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "repo-a" in out
|
||||
|
||||
|
||||
def test_cmd_report_reuse_apply_calls_apply_patches(monkeypatch):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reuse_events", lambda hub_url=None: SAMPLE_REUSE_EVENTS)
|
||||
suggestion = {"capability_id": "capability.infotech.issue-tracking", "kind": "relation_add", "detail": "x", "value": {"type": "reused_by", "target": "repo-a"}}
|
||||
monkeypatch.setattr("reuse_surface.cli.collect_reused_by_suggestions", lambda report: [suggestion])
|
||||
applied_calls = []
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.patches.apply_patches",
|
||||
lambda repo_root, patches: applied_calls.append(patches) or ["applied one"],
|
||||
)
|
||||
exit_code = main(["report", "reuse", "--suggest-relations", "--apply"])
|
||||
assert exit_code == 0
|
||||
assert applied_calls == [[suggestion]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue