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

@ -200,6 +200,21 @@ def create_app() -> FastAPI:
return JSONResponse(content={"accepted": True, "composed_at": composed_at})
@app.post("/v1/reuse-events", status_code=201, dependencies=[Depends(_require_auth)])
async def record_reuse_event(request: Request) -> dict[str, Any]:
payload = await request.json()
try:
return store.record_reuse_event(payload)
except ValueError as exc:
raise _http_error(400, "validation_error", str(exc)) from exc
@app.get("/v1/reuse-events")
def list_reuse_events(
capability_id: str | None = Query(default=None),
) -> dict[str, Any]:
events = store.list_reuse_events(capability_id)
return {"count": len(events), "events": events}
return app