Files
inter-hub/workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md
Bernd Worsch 87049dfc1f
Some checks failed
Test / test (push) Has been cancelled
chore: register Phase 2 workplan (IHUB-WP-0002)
IHF Phase 2 — Structured Feedback and Triage. 9 tasks (T01–T09)
covering AnnotationThread, RequirementCandidate, TriageState,
ReviewerAssignment, escalation action, and triage dashboard.
Workstream registered in State Hub under inter_hub topic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 01:00:03 +00:00

324 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: IHUB-WP-0002
type: workplan
title: "IHF Phase 2 — Structured Feedback and Triage"
domain: inter_hub
repo: inter-hub
status: active
owner: custodian
topic_slug: inter_hub
created: "2026-03-28"
updated: "2026-03-28"
state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7"
---
# IHF Phase 2 — Structured Feedback and Triage
## Goal
Transform raw annotations into structured, operable feedback. Phase 1 established the
capture layer (widgets, events, annotations). Phase 2 makes that feedback governable:
grouping related observations, escalating them into Requirement Candidates, tracking
triage status and reviewer ownership, and surfacing the triage queue to operators in
a live dashboard.
## Background
Phase 1 (IHUB-WP-0001) delivered the Minimal Interaction Core — widget registry,
interaction event capture, annotation system, and hub operator dashboard. All Phase 1
exit criteria are met.
Phase 2 is the second of eight phases in the IHF specification
(`specs/InteractionHubFrameworkSpecification_v0.1.md`). It extends the Phase 1
annotation model with severity markers, threading/grouping, escalation to Requirement
Candidates, a triage lifecycle, and reviewer assignment.
**Technology stack:** IHP v1.5 (Haskell, Nix), PostgreSQL, AutoRefresh (triage
dashboard), HTMX (escalation and triage actions), IHP forms (CRUD).
Reference: `docs/ihp-overview.md`, `docs/ihp-data-and-queries.md`,
`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`.
## Phase 2 Exit Criteria (from IHF spec §14 Phase 2)
- Feedback volume can be triaged rather than merely stored
- Multiple related comments can converge into a structured candidate
- Reviewers can track status and ownership
## Data Artifacts Introduced (Phase 2)
`AnnotationThread`, `RequirementCandidate`, `TriageState`, `ReviewerAssignment`
Also extends: `Annotation` (adds `severity`, `thread_id`)
---
## Tasks
### T01 — Schema: AnnotationThread, RequirementCandidate, TriageState, ReviewerAssignment
```task
id: IHUB-WP-0002-T01
status: todo
priority: high
state_hub_task_id: "eb267a9e-7e80-4913-b7a3-7f5adb04a0f2"
```
Add Phase 2 tables to `Application/Schema.sql` and write migration:
```sql
-- Extend annotations with severity and thread grouping
ALTER TABLE annotations ADD COLUMN severity TEXT NOT NULL DEFAULT 'medium';
ALTER TABLE annotations ADD COLUMN thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL;
CREATE TABLE annotation_threads (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE TABLE requirement_candidates (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL,
source_widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE RESTRICT,
source_thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL,
source_annotation_id UUID REFERENCES annotations(id) ON DELETE SET NULL,
category TEXT NOT NULL DEFAULT 'friction',
status TEXT NOT NULL DEFAULT 'open',
created_by UUID REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE INDEX requirement_candidates_widget_id_idx ON requirement_candidates (source_widget_id);
CREATE INDEX requirement_candidates_status_idx ON requirement_candidates (status);
CREATE TABLE triage_states (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE,
status TEXT NOT NULL,
notes TEXT,
changed_by UUID REFERENCES users(id),
changed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE INDEX triage_states_candidate_id_idx ON triage_states (candidate_id);
CREATE TABLE reviewer_assignments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
assigned_by UUID REFERENCES users(id),
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
UNIQUE (candidate_id)
);
```
- Valid `severity` values: `low`, `medium`, `high`, `critical`
- Valid `requirement_candidates.status` values: `open`, `in_review`, `accepted`, `rejected`, `deferred`
- Verify Haskell types are generated correctly
**Exit criteria:** `migrate` runs cleanly; all Phase 2 types available in GHCi.
---
### T02 — Severity markers on Annotation
```task
id: IHUB-WP-0002-T02
status: todo
priority: high
state_hub_task_id: "fdcbf823-484e-4f0f-a0ca-28f9222520af"
```
1. Update `CreateAnnotationAction` to bind and validate `severity` (low/medium/high/critical, default medium)
2. Update annotation form: add severity select field
3. Update annotation list and show views to display severity with Tailwind color cues:
- `low` → muted/secondary
- `medium` → info/neutral
- `high` → warning
- `critical` → error/destructive (per `specs/TailwindForInteractionHubs_v0.2.md` color roles)
**Exit criteria:** New annotations carry severity; existing annotations default to `medium`; severity is visible in all annotation views.
---
### T03 — AnnotationThread controller (duplicate grouping)
```task
id: IHUB-WP-0002-T03
status: todo
priority: high
state_hub_task_id: "35b989a0-5e2a-4300-990b-f43d67de0727"
```
1. Scaffold `AnnotationThreadsController` scoped to a widget: `/widgets/:widgetId/threads/`
2. Actions: `IndexAnnotationThreadsAction`, `ShowAnnotationThreadAction`, `NewAnnotationThreadAction`, `CreateAnnotationThreadAction`
3. `CreateAnnotationThreadAction`: creates thread with `title`, `description`, `widgetId`
4. Add `AssignAnnotationToThreadAction` (HTMX POST): sets `annotation.thread_id`
5. Thread show view: list of member annotations with severity distribution bar and dominant category badge
6. Thread list view: thread title, annotation count, severity breakdown, created_at
**Exit criteria:** Operator can create a thread, assign annotations to it, and view the grouped observations.
---
### T04 — RequirementCandidate controller and views
```task
id: IHUB-WP-0002-T04
status: todo
priority: high
state_hub_task_id: "4eb2a51c-1b3f-4b36-b945-6bfb14c2e680"
```
1. Scaffold `RequirementCandidatesController`
2. Actions: index, show, new, create, edit, update (no delete)
3. Fields: `title`, `description`, `sourceWidgetId` (select), `sourceThreadId` (optional select), `category` (select), initial status `open`
4. Index view: table with status badge, widget, category, reviewer name, created_at; filterable by status
5. Show view: full detail + linked source (annotation or thread) + triage history log + current reviewer
**Exit criteria:** Requirement candidates can be created manually, listed, filtered by status, and viewed with full context.
---
### T05 — Escalation action: Annotation(s) → RequirementCandidate
```task
id: IHUB-WP-0002-T05
status: todo
priority: high
state_hub_task_id: "5c3a154b-38e0-4e40-9e97-57aae1dbc95d"
```
1. Add "Escalate to Candidate" button on annotation show page (HTMX POST to `EscalateAnnotationAction { annotationId }`)
2. Action pre-populates and creates a `RequirementCandidate` with:
- `title` derived from annotation body (first 80 chars)
- `category` copied from annotation category
- `sourceAnnotationId` set
- `sourceWidgetId` from annotation's widget
3. Respond with HTMX swap: replace escalate button with "Escalated →" link to the candidate
4. Add visual indicator on escalated annotations in annotation lists
**Exit criteria:** Single-click escalation creates a linked candidate; escalated annotations are visually distinct; no duplicate escalation possible (button replaced after action).
---
### T06 — TriageState lifecycle per RequirementCandidate
```task
id: IHUB-WP-0002-T06
status: todo
priority: high
state_hub_task_id: "cd8c3ef1-e0f7-435f-ae20-e0760df5da83"
```
1. `UpdateTriageStatusAction { candidateId, newStatus, notes }` (HTMX POST)
2. Validate allowed transitions:
- `open``in_review`
- `in_review``accepted`, `rejected`, `deferred`
- `deferred``in_review`
- All others rejected with 422
3. On valid transition: insert `TriageState` row, update `requirement_candidates.status`
4. Show page: render full triage history as an audit trail (status, changed_by, changed_at, notes)
5. Status badge in index updates live via AutoRefresh
**Exit criteria:** Triage lifecycle enforced; invalid transitions rejected; full audit trail visible on show page.
---
### T07 — ReviewerAssignment: ownership on RequirementCandidate
```task
id: IHUB-WP-0002-T07
status: todo
priority: medium
state_hub_task_id: "3dc9bfdb-06d0-48a5-8973-2e39c6e0f78a"
```
1. `AssignReviewerAction { candidateId, userId }` (HTMX POST from candidate show page)
2. Upsert `ReviewerAssignment` (one reviewer per candidate; replacing previous assignment)
3. Show current reviewer name on candidate index and show pages
4. Add "My Queue" view: `MyQueueAction` — lists all open/in_review candidates assigned to `currentUser`
5. Index view: filter by "Unassigned" and "Assigned to me"
**Exit criteria:** Candidates can be assigned to reviewers; reviewer sees their queue; unassigned candidates are identifiable.
---
### T08 — Operator triage dashboard (AutoRefresh)
```task
id: IHUB-WP-0002-T08
status: todo
priority: high
state_hub_task_id: "82498422-1626-4479-9daa-3d7c7e088d8e"
```
1. Add `TriageDashboardAction` (or extend `ShowHubAction`) wrapped with `autoRefresh do`
2. Dashboard panels:
- KPI row: open / in_review / accepted / rejected / deferred counts
- Triage queue: open candidates sorted oldest-first (title, widget, category, age)
- Recent escalations: last 20 with widget and source annotation snippet
- Category breakdown: annotation count grouped by category across all hub widgets
3. Layout includes `{autoRefreshMeta}`, `morphdom.js`, `ihp-auto-refresh.js`
4. Test: insert a new candidate via the UI → dashboard updates within ~1s without reload
**Exit criteria:** Dashboard live-updates on candidate/triage changes. WebSocket frames visible in DevTools.
---
### T09 — Phase 2 gate: tests, consistency, docs
```task
id: IHUB-WP-0002-T09
status: todo
priority: high
state_hub_task_id: "935de4d7-867f-49aa-bddf-6ff9435215de"
```
1. **Integration tests** (`Test/`):
- AnnotationThread create + assign annotation
- Escalation: annotation → candidate, duplicate escalation blocked
- Triage transitions: valid path (open → in_review → accepted); invalid transition rejected (422)
- ReviewerAssignment: assign, reassign, my-queue filter
- Triage dashboard: `autoRefresh` wrapper present
2. **Consistency sync:**
```bash
cd ~/the-custodian && make fix-consistency REPO=inter-hub
```
3. **Documentation updates:**
- Update `SCOPE.md` current state section: Phase 2 complete
- Write `docs/phase2-summary.md`: what was built, known limitations, Phase 3 readiness
4. **Smoke test checklist:**
- `devenv up` → clean start
- Annotate a widget, set severity high
- Create a thread, assign two annotations to it
- Escalate an annotation to a candidate
- Run triage: open → in_review → accepted
- Assign reviewer, check My Queue
- Confirm triage dashboard auto-updates
**Exit criteria:** All tests pass; consistency sync reports no errors; smoke test completed; SCOPE.md updated.
---
## Phase 2 Dependencies
- Phase 1 schema stable (T01 depends on widgets, annotations, users from Phase 1)
- `annotation_threads` before `annotations` FK extension (T01 ordering: create thread table before altering annotations)
- Schema (T01) before all controller work (T02T08)
- RequirementCandidate (T04) before Escalation (T05), TriageState (T06), ReviewerAssignment (T07)
- All feature tasks (T01T08) before gate (T09)
## Notes
- **Severity on Annotation is a Phase 1 schema extension**, not a new table — it's a simple migration with a safe default.
- **No automated duplicate detection in Phase 2.** Grouping is operator-driven via `AnnotationThread`. Automated similarity is Phase 3+ scope.
- **TriageState is append-only.** Like `InteractionEvent`, never update/delete triage rows — only insert new status transitions.
- **HTMX for in-place actions** (escalate, triage status, assign reviewer) keeps the operator flow fluid without full page reloads.
- **No `RequirementCandidate` delete.** Candidates can be rejected or archived, never deleted, to preserve audit trail.