REUSE-WP-0019-T04: reuse telemetry store and recording
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 10s
ci / validate-registry (push) Has been cancelled
Build and Publish Container Image / build-and-push (push) Successful in 1m16s

Implements the hub side of the shared reuse-event schema (already drafted
in WP-0018-T01, schemas/reuse-event.schema.json): a SQLite reuse_events
table, POST /v1/reuse-events (token-auth), GET /v1/reuse-events?capability_id=
(read-only).

reuse_surface/plan_check.py: refactored record_outcome around a new
shared post_or_fallback_reuse_event() helper -- tries the hub first, falls
back to the local JSONL only on failure/unreachability, never both. New
record_manual_reuse_event() backs a new CLI command, reuse-surface
record-reuse, for retroactive facts recorded outside plan-check.

Privacy/scope (repo slugs and capability ids only, no code, no secrets) is
enforced structurally via the schema's additionalProperties: false, not
just by convention.

21 new pytest cases, 145 total pass. Live-verified against a real running
hub instance: POST/GET /v1/reuse-events directly, record-reuse and
plan-check --record-outcome both posting successfully to the hub, and --
after actually killing the hub process -- confirmed the fallback path
writes correctly to the local JSONL instead of erroring.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-07 22:32:19 +02:00
parent 2fcc91f2aa
commit d181043717
12 changed files with 562 additions and 32 deletions

View file

@ -311,4 +311,86 @@ def test_webhook_accepts_gitea_signature_header(webhook_client):
content=body,
headers={"X-Gitea-Signature": _sign(body), "Content-Type": "application/json"},
)
assert response.status_code == 200
assert response.status_code == 200
# --- T04: reuse telemetry store ---
VALID_REUSE_EVENT = {
"ts": "2026-07-08T00:00:00Z",
"consumer_repo": "some-repo",
"capability_id": "capability.infotech.issue-tracking",
"verdict": "reuse",
"outcome": "reused",
"source": "plan-check",
}
def test_record_reuse_event_requires_auth(hub_client):
response = hub_client.post("/v1/reuse-events", json=VALID_REUSE_EVENT)
assert response.status_code == 401
def test_record_reuse_event_and_list(hub_client):
response = hub_client.post(
"/v1/reuse-events",
json=VALID_REUSE_EVENT,
headers={"Authorization": "Bearer test-token"},
)
assert response.status_code == 201
listed = hub_client.get("/v1/reuse-events")
assert listed.status_code == 200
assert listed.json()["count"] == 1
assert listed.json()["events"][0]["consumer_repo"] == "some-repo"
def test_record_reuse_event_rejects_invalid_verdict(hub_client):
bad = {**VALID_REUSE_EVENT, "verdict": "not-a-verdict"}
response = hub_client.post(
"/v1/reuse-events",
json=bad,
headers={"Authorization": "Bearer test-token"},
)
assert response.status_code == 400
def test_record_reuse_event_rejects_extra_fields(hub_client):
bad = {**VALID_REUSE_EVENT, "unexpected_field": "nope"}
response = hub_client.post(
"/v1/reuse-events",
json=bad,
headers={"Authorization": "Bearer test-token"},
)
assert response.status_code == 400
def test_list_reuse_events_filters_by_capability_id(hub_client):
other = {**VALID_REUSE_EVENT, "capability_id": "capability.infotech.other"}
hub_client.post("/v1/reuse-events", json=VALID_REUSE_EVENT, headers={"Authorization": "Bearer test-token"})
hub_client.post("/v1/reuse-events", json=other, headers={"Authorization": "Bearer test-token"})
listed = hub_client.get("/v1/reuse-events?capability_id=capability.infotech.other")
assert listed.status_code == 200
assert listed.json()["count"] == 1
assert listed.json()["events"][0]["capability_id"] == "capability.infotech.other"
def test_list_reuse_events_empty_by_default(hub_client):
listed = hub_client.get("/v1/reuse-events")
assert listed.status_code == 200
assert listed.json() == {"count": 0, "events": []}
def test_store_record_reuse_event_and_list(tmp_path):
store = HubStore(tmp_path / "hub.db")
store.record_reuse_event(VALID_REUSE_EVENT)
events = store.list_reuse_events()
assert len(events) == 1
assert events[0]["consumer_repo"] == "some-repo"
def test_store_record_reuse_event_rejects_invalid(tmp_path):
store = HubStore(tmp_path / "hub.db")
with pytest.raises(ValueError):
store.record_reuse_event({**VALID_REUSE_EVENT, "verdict": "nope"})