graph: surface referrers + deletions in service + dashboard (closes #205) - #209
Conversation
Completes #205: exposes the referrer/deletion data (from #201/#202, queried in #208) through the graph-service HTTP API and the dashboard. - graph-service: new GET /artifacts/referrers (json|cytoscape|mermaid); the existing /artifacts/show already returns referrers + the deletion tombstone. - dashboard-web: GraphClient.referrers + a /graph/referrers htmx route + a 'List referrer artifacts' form on the observability view; deleted occurrences now get a 'deleted' badge in the neighborhood node list. - Tests: graph-service 18, dashboard-web 25 (referrers endpoint/route/ client + deletion marker). Closes #205
There was a problem hiding this comment.
Pull request overview
Adds end-to-end surfacing of referrer relationships (REFERS_TO edges with artifactType) and deleted-occurrence visibility across the graph-service HTTP API and the dashboard observability UI, building on the shared cssc_graph.queries layer introduced earlier.
Changes:
- Added
GET /artifacts/referrersto graph-service withref/digest,depth, andformat=json|cytoscape|mermaid. - Added dashboard support:
GraphServiceClient.referrers, a/graph/referrersHTMX route, and a new partial template to render referrer edges (artifact type → referrer → subject). - Updated neighborhood UI to badge deleted occurrences (
deletedAt).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| apps/python-app/services/graph-service/tests/test_app.py | Adds fixture data and tests covering the new /artifacts/referrers endpoint and selector validation. |
| apps/python-app/services/graph-service/src/graph_service/app.py | Implements the new FastAPI endpoint and format handling (json/cytoscape/mermaid). |
| apps/python-app/services/dashboard-web/tests/test_graph.py | Adds route and client tests for referrers rendering and the client call shape. |
| apps/python-app/services/dashboard-web/src/dashboard_web/web/routes.py | Adds /graph/referrers HTMX route with error handling consistent with neighborhood. |
| apps/python-app/services/dashboard-web/src/dashboard_web/templates/stages/observability.html | Adds a “List referrer artifacts” form and target container for HTMX rendering. |
| apps/python-app/services/dashboard-web/src/dashboard_web/templates/stages/_graph_referrers.html | New template to render referrer edges including artifactType. |
| apps/python-app/services/dashboard-web/src/dashboard_web/templates/stages/_graph_neighborhood.html | Adds a “deleted” badge for nodes with deletedAt. |
| apps/python-app/services/dashboard-web/src/dashboard_web/clients.py | Extends the GraphClient protocol and GraphServiceClient with a referrers() call. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
apps/python-app/services/graph-service/tests/test_app.py:219
- The new
/artifacts/referrersendpoint supportsformat=mermaid|cytoscape, but the test suite only validates the default JSON response. Adding basic assertions for these formats would prevent regressions (content-type for mermaid;elementspayload for cytoscape), mirroring the existing neighborhood format tests.
def test_referrers_endpoint(client: TestClient) -> None:
body = client.get("/artifacts/referrers", params={"ref": f"{APP_REF}@{DIGEST_APP}"}).json()
assert any(
e["type"] == "REFERS_TO" and e.get("artifactType") == "application/vnd.in-toto+json"
for e in body["edges"]
)
def test_referrers_requires_selector(client: TestClient) -> None:
assert client.get("/artifacts/referrers").status_code == 400
apps/python-app/services/dashboard-web/tests/test_graph.py:128
- The neighborhood UI now adds a "deleted" badge when
deletedAtis present, but dashboard-web tests don’t cover this behavior. Adding a small route-level test with a node that includesdeletedAtwould prevent accidental template regressions.
def test_referrers_route_renders_artifact_type():
client = TestClient(_app(FakeGraph()))
resp = client.get("/graph/referrers", params={"ref": "ghcr.io/toddysm/golden/python:3.14-slim"})
assert resp.status_code == 200
assert "application/vnd.in-toto+json" in resp.text
def test_referrers_route_without_ref_prompts():
client = TestClient(_app(FakeGraph()))
assert "list its referrer artifacts" in client.get("/graph/referrers").text
def test_referrers_route_empty():
client = TestClient(_app(FakeGraph(referrers={"nodes": [], "edges": []})))
body = client.get("/graph/referrers", params={"ref": "ghcr.io/none:0"}).text
assert "No referrers found" in body
Completes #205 — the graph-service + dashboard slice (the CLI/query core landed in #208).
What
Exposes the referrer/deletion data (indexed in #201/#202) through the HTTP API and the UI:
GET /artifacts/referrers(ref/digest,depth,format=json|cytoscape|mermaid)./artifacts/showalready returnsreferrers+ the deletion tombstone via the shared query layer.GraphClient.referrers+ a/graph/referrershtmx route + a List referrer artifacts form on the observability view (renders artifactType → referrer → subject). Deleted occurrences now get a deleted badge in the neighborhood node list.Validation
graph-service: 18 passed (new: referrers endpoint returns theREFERS_TOedge with artifactType; requires a selector → 400). Added aReferrerObservedrecord to the test data root.dashboard-web: 25 passed (referrers route renders artifactType, empty/no-ref states, and theGraphServiceClient.referrersclient call).With this, #205 is complete: referrers and deletions are queryable via the CLI (#208), the HTTP API, and the dashboard.
Closes #205