generated from coulomb/repo-seed
chore(consistency): add IHUB-WP-0002 workplan file and fix repo path
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Creates the missing Phase 2 workplan file to resolve C-07 ADR-001 violation (active DB workstream with no backing file). Also corrects the registered repo path from /home/tegwick to /home/worsch on host bnt-lap001. Consistency check now PASS (0 fail, 0 warn). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
id: IHUB-WP-0002
|
||||
type: workplan
|
||||
title: "IHF Phase 2 — Structured Feedback and Triage"
|
||||
domain: inter_hub
|
||||
domain: custodian
|
||||
repo: inter-hub
|
||||
status: active
|
||||
owner: custodian
|
||||
topic_slug: inter_hub
|
||||
topic_slug: custodian
|
||||
created: "2026-03-28"
|
||||
updated: "2026-03-28"
|
||||
state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7"
|
||||
@@ -16,41 +16,39 @@ state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7"
|
||||
|
||||
## 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.
|
||||
Transform raw annotations into structured, operable feedback. Introduces
|
||||
AnnotationThread (grouping), RequirementCandidate (escalation), TriageState
|
||||
(lifecycle), and ReviewerAssignment (ownership). Operator triage dashboard
|
||||
with AutoRefresh.
|
||||
|
||||
## 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 1 delivered the minimal interaction core: widget registry, interaction
|
||||
event capture, annotation system, and hub-level operator dashboard. Phase 2
|
||||
closes the gap between raw observation and structured, actionable feedback —
|
||||
enabling operators to group annotations, escalate them into requirement
|
||||
candidates, and drive them through a triage lifecycle.
|
||||
|
||||
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).
|
||||
**Entry criteria:** Phase 1 complete (T01–T12 all done, WP-0001 status=done).
|
||||
|
||||
Reference: `docs/ihp-overview.md`, `docs/ihp-data-and-queries.md`,
|
||||
`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`.
|
||||
`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`,
|
||||
`docs/ihp-ihf-mapping.md`.
|
||||
|
||||
## Phase 2 Exit Criteria (from IHF spec §14 Phase 2)
|
||||
## Phase 2 Exit Criteria
|
||||
|
||||
- Feedback volume can be triaged rather than merely stored
|
||||
- Multiple related comments can converge into a structured candidate
|
||||
- Reviewers can track status and ownership
|
||||
- Annotations can be grouped into AnnotationThreads
|
||||
- Annotations can be escalated into RequirementCandidates
|
||||
- RequirementCandidates have a full triage lifecycle (open → in_review → accepted/rejected/deferred)
|
||||
- Reviewers can be assigned to candidates
|
||||
- Operator triage dashboard live-updates via AutoRefresh
|
||||
- All integration tests passing
|
||||
- SCOPE.md updated to reflect Phase 2 completion
|
||||
|
||||
## Data Artifacts Introduced (Phase 2)
|
||||
|
||||
`AnnotationThread`, `RequirementCandidate`, `TriageState`, `ReviewerAssignment`
|
||||
|
||||
Also extends: `Annotation` (adds `severity`, `thread_id`)
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
@@ -64,64 +62,17 @@ priority: high
|
||||
state_hub_task_id: "eb267a9e-7e80-4913-b7a3-7f5adb04a0f2"
|
||||
```
|
||||
|
||||
Add Phase 2 tables to `Application/Schema.sql` and write migration:
|
||||
Add Phase 2 tables to `Application/Schema.sql` and write migration. Add
|
||||
severity/intensity field to annotations table. Define:
|
||||
|
||||
```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;
|
||||
- `AnnotationThread` — grouping of related annotations
|
||||
- `RequirementCandidate` — escalated feedback record
|
||||
- `TriageState` — per-candidate lifecycle row
|
||||
- `ReviewerAssignment` — ownership record
|
||||
|
||||
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
|
||||
);
|
||||
Generate IHP types via the IDE code generator.
|
||||
|
||||
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.
|
||||
**Exit criteria:** `migrate` succeeds; IHP generated types compile cleanly.
|
||||
|
||||
---
|
||||
|
||||
@@ -134,15 +85,13 @@ 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)
|
||||
Add severity field (low/medium/high/critical) to annotations table via
|
||||
migration. Update `AnnotationsController` `CreateAnnotationAction` to accept
|
||||
and validate severity. Update annotation form and list/show views to display
|
||||
severity with appropriate visual cues (Tailwind color roles per spec).
|
||||
|
||||
**Exit criteria:** New annotations carry severity; existing annotations default to `medium`; severity is visible in all annotation views.
|
||||
**Exit criteria:** Annotations can be created with a severity; severity renders
|
||||
with correct color coding in list and show views.
|
||||
|
||||
---
|
||||
|
||||
@@ -155,14 +104,13 @@ 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
|
||||
Scaffold `AnnotationThreadsController`. Allow operator to create a named thread
|
||||
and manually assign annotations to it (many-to-one via `annotation.thread_id`
|
||||
FK). Thread list view shows member annotation count, severity distribution, and
|
||||
dominant category. Enables duplicate/similar-observation grouping.
|
||||
|
||||
**Exit criteria:** Operator can create a thread, assign annotations to it, and view the grouped observations.
|
||||
**Exit criteria:** Threads can be created; annotations can be assigned to a
|
||||
thread; thread list shows per-thread aggregates.
|
||||
|
||||
---
|
||||
|
||||
@@ -175,13 +123,14 @@ 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
|
||||
Scaffold `RequirementCandidatesController`. CRUD: index, show, new/create,
|
||||
edit/update (no delete). Fields: title, description, source_widget_id,
|
||||
source_thread_id (optional), category, status (open). Index: list with status,
|
||||
widget, category, reviewer. Show: full detail + linked annotations + triage
|
||||
history.
|
||||
|
||||
**Exit criteria:** Requirement candidates can be created manually, listed, filtered by status, and viewed with full context.
|
||||
**Exit criteria:** Candidates can be created manually; index and show pages
|
||||
render correctly.
|
||||
|
||||
---
|
||||
|
||||
@@ -194,16 +143,13 @@ 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
|
||||
Add `EscalateAnnotationAction` (HTMX POST) on the annotation show/list page.
|
||||
Pre-populates a new RequirementCandidate form with the annotation body and
|
||||
category. On submit, creates the candidate and records `source_annotation_id`.
|
||||
Add a visual indicator on escalated annotations.
|
||||
|
||||
**Exit criteria:** Single-click escalation creates a linked candidate; escalated annotations are visually distinct; no duplicate escalation possible (button replaced after action).
|
||||
**Exit criteria:** Operator can escalate an annotation to a candidate in one
|
||||
action; escalated annotations show a visual marker.
|
||||
|
||||
---
|
||||
|
||||
@@ -216,17 +162,13 @@ 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
|
||||
Implement TriageState table (candidate_id, status, changed_by, changed_at,
|
||||
notes). Status lifecycle: open → in_review → accepted | rejected | deferred.
|
||||
Add `UpdateTriageStatusAction` (HTMX). Show full triage history on
|
||||
RequirementCandidate show page. Validate allowed transitions.
|
||||
|
||||
**Exit criteria:** Triage lifecycle enforced; invalid transitions rejected; full audit trail visible on show page.
|
||||
**Exit criteria:** Status transitions work; invalid transitions are rejected;
|
||||
full history visible on candidate show page.
|
||||
|
||||
---
|
||||
|
||||
@@ -239,13 +181,13 @@ 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"
|
||||
Implement ReviewerAssignment (candidate_id, user_id, assigned_by, assigned_at).
|
||||
Add `AssignReviewerAction`. Show current reviewer on candidate index and show
|
||||
pages. Filter index by assignee. Reviewer can see their open candidates via a
|
||||
"My Queue" view under their session.
|
||||
|
||||
**Exit criteria:** Candidates can be assigned to reviewers; reviewer sees their queue; unassigned candidates are identifiable.
|
||||
**Exit criteria:** Reviewer can be assigned; "My Queue" shows open candidates
|
||||
for the logged-in reviewer.
|
||||
|
||||
---
|
||||
|
||||
@@ -258,16 +200,13 @@ 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
|
||||
Extend or add a dedicated triage dashboard action wrapped with `autoRefresh do`.
|
||||
Shows: open RequirementCandidates (count by status), triage queue (oldest open
|
||||
first), recent escalations (last 20), candidates by category breakdown.
|
||||
Live-updates on any candidate/triage state change.
|
||||
|
||||
**Exit criteria:** Dashboard live-updates on candidate/triage changes. WebSocket frames visible in DevTools.
|
||||
**Exit criteria:** Dashboard renders; live-updates on candidate creation and
|
||||
triage status change without page reload.
|
||||
|
||||
---
|
||||
|
||||
@@ -280,44 +219,11 @@ 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
|
||||
Integration tests: AnnotationThread CRUD; escalation happy path; triage state
|
||||
transitions (valid + invalid); reviewer assignment; dashboard AutoRefresh
|
||||
wrapper present. Consistency sync. Update `SCOPE.md` current state. Write
|
||||
`docs/phase2-summary.md`. Smoke test: create thread, escalate annotation, triage
|
||||
candidate, confirm dashboard 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 (T02–T08)
|
||||
- RequirementCandidate (T04) before Escalation (T05), TriageState (T06), ReviewerAssignment (T07)
|
||||
- All feature tasks (T01–T08) 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.
|
||||
**Exit criteria:** All integration tests pass; `SCOPE.md` reflects Phase 2
|
||||
completion; consistency check passes.
|
||||
|
||||
Reference in New Issue
Block a user