chore(consistency): add IHUB-WP-0002 workplan file and fix repo path
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:
2026-03-28 22:47:36 +01:00
parent af9c3c1d21
commit cfcf4c81f7

View File

@@ -2,11 +2,11 @@
id: IHUB-WP-0002 id: IHUB-WP-0002
type: workplan type: workplan
title: "IHF Phase 2 — Structured Feedback and Triage" title: "IHF Phase 2 — Structured Feedback and Triage"
domain: inter_hub domain: custodian
repo: inter-hub repo: inter-hub
status: active status: active
owner: custodian owner: custodian
topic_slug: inter_hub topic_slug: custodian
created: "2026-03-28" created: "2026-03-28"
updated: "2026-03-28" updated: "2026-03-28"
state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7" state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7"
@@ -16,41 +16,39 @@ state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7"
## Goal ## Goal
Transform raw annotations into structured, operable feedback. Phase 1 established the Transform raw annotations into structured, operable feedback. Introduces
capture layer (widgets, events, annotations). Phase 2 makes that feedback governable: AnnotationThread (grouping), RequirementCandidate (escalation), TriageState
grouping related observations, escalating them into Requirement Candidates, tracking (lifecycle), and ReviewerAssignment (ownership). Operator triage dashboard
triage status and reviewer ownership, and surfacing the triage queue to operators in with AutoRefresh.
a live dashboard.
## Background ## Background
Phase 1 (IHUB-WP-0001) delivered the Minimal Interaction Core widget registry, Phase 1 delivered the minimal interaction core: widget registry, interaction
interaction event capture, annotation system, and hub operator dashboard. All Phase 1 event capture, annotation system, and hub-level operator dashboard. Phase 2
exit criteria are met. 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 **Entry criteria:** Phase 1 complete (T01T12 all done, WP-0001 status=done).
(`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`, 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 - Annotations can be grouped into AnnotationThreads
- Multiple related comments can converge into a structured candidate - Annotations can be escalated into RequirementCandidates
- Reviewers can track status and ownership - 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) ## Data Artifacts Introduced (Phase 2)
`AnnotationThread`, `RequirementCandidate`, `TriageState`, `ReviewerAssignment` `AnnotationThread`, `RequirementCandidate`, `TriageState`, `ReviewerAssignment`
Also extends: `Annotation` (adds `severity`, `thread_id`)
--- ---
## Tasks ## Tasks
@@ -64,64 +62,17 @@ priority: high
state_hub_task_id: "eb267a9e-7e80-4913-b7a3-7f5adb04a0f2" 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 - `AnnotationThread` — grouping of related annotations
-- Extend annotations with severity and thread grouping - `RequirementCandidate` — escalated feedback record
ALTER TABLE annotations ADD COLUMN severity TEXT NOT NULL DEFAULT 'medium'; - `TriageState` — per-candidate lifecycle row
ALTER TABLE annotations ADD COLUMN thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL; - `ReviewerAssignment` — ownership record
CREATE TABLE annotation_threads ( Generate IHP types via the IDE code generator.
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 ( **Exit criteria:** `migrate` succeeds; IHP generated types compile cleanly.
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.
--- ---
@@ -134,15 +85,13 @@ priority: high
state_hub_task_id: "fdcbf823-484e-4f0f-a0ca-28f9222520af" state_hub_task_id: "fdcbf823-484e-4f0f-a0ca-28f9222520af"
``` ```
1. Update `CreateAnnotationAction` to bind and validate `severity` (low/medium/high/critical, default medium) Add severity field (low/medium/high/critical) to annotations table via
2. Update annotation form: add severity select field migration. Update `AnnotationsController` `CreateAnnotationAction` to accept
3. Update annotation list and show views to display severity with Tailwind color cues: and validate severity. Update annotation form and list/show views to display
- `low` → muted/secondary severity with appropriate visual cues (Tailwind color roles per spec).
- `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. **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" state_hub_task_id: "35b989a0-5e2a-4300-990b-f43d67de0727"
``` ```
1. Scaffold `AnnotationThreadsController` scoped to a widget: `/widgets/:widgetId/threads/` Scaffold `AnnotationThreadsController`. Allow operator to create a named thread
2. Actions: `IndexAnnotationThreadsAction`, `ShowAnnotationThreadAction`, `NewAnnotationThreadAction`, `CreateAnnotationThreadAction` and manually assign annotations to it (many-to-one via `annotation.thread_id`
3. `CreateAnnotationThreadAction`: creates thread with `title`, `description`, `widgetId` FK). Thread list view shows member annotation count, severity distribution, and
4. Add `AssignAnnotationToThreadAction` (HTMX POST): sets `annotation.thread_id` dominant category. Enables duplicate/similar-observation grouping.
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. **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" state_hub_task_id: "4eb2a51c-1b3f-4b36-b945-6bfb14c2e680"
``` ```
1. Scaffold `RequirementCandidatesController` Scaffold `RequirementCandidatesController`. CRUD: index, show, new/create,
2. Actions: index, show, new, create, edit, update (no delete) edit/update (no delete). Fields: title, description, source_widget_id,
3. Fields: `title`, `description`, `sourceWidgetId` (select), `sourceThreadId` (optional select), `category` (select), initial status `open` source_thread_id (optional), category, status (open). Index: list with status,
4. Index view: table with status badge, widget, category, reviewer name, created_at; filterable by status widget, category, reviewer. Show: full detail + linked annotations + triage
5. Show view: full detail + linked source (annotation or thread) + triage history log + current reviewer 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" state_hub_task_id: "5c3a154b-38e0-4e40-9e97-57aae1dbc95d"
``` ```
1. Add "Escalate to Candidate" button on annotation show page (HTMX POST to `EscalateAnnotationAction { annotationId }`) Add `EscalateAnnotationAction` (HTMX POST) on the annotation show/list page.
2. Action pre-populates and creates a `RequirementCandidate` with: Pre-populates a new RequirementCandidate form with the annotation body and
- `title` derived from annotation body (first 80 chars) category. On submit, creates the candidate and records `source_annotation_id`.
- `category` copied from annotation category Add a visual indicator on escalated annotations.
- `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). **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" state_hub_task_id: "cd8c3ef1-e0f7-435f-ae20-e0760df5da83"
``` ```
1. `UpdateTriageStatusAction { candidateId, newStatus, notes }` (HTMX POST) Implement TriageState table (candidate_id, status, changed_by, changed_at,
2. Validate allowed transitions: notes). Status lifecycle: open → in_review → accepted | rejected | deferred.
- `open``in_review` Add `UpdateTriageStatusAction` (HTMX). Show full triage history on
- `in_review``accepted`, `rejected`, `deferred` RequirementCandidate show page. Validate allowed transitions.
- `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. **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" state_hub_task_id: "3dc9bfdb-06d0-48a5-8973-2e39c6e0f78a"
``` ```
1. `AssignReviewerAction { candidateId, userId }` (HTMX POST from candidate show page) Implement ReviewerAssignment (candidate_id, user_id, assigned_by, assigned_at).
2. Upsert `ReviewerAssignment` (one reviewer per candidate; replacing previous assignment) Add `AssignReviewerAction`. Show current reviewer on candidate index and show
3. Show current reviewer name on candidate index and show pages pages. Filter index by assignee. Reviewer can see their open candidates via a
4. Add "My Queue" view: `MyQueueAction` — lists all open/in_review candidates assigned to `currentUser` "My Queue" view under their session.
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. **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" state_hub_task_id: "82498422-1626-4479-9daa-3d7c7e088d8e"
``` ```
1. Add `TriageDashboardAction` (or extend `ShowHubAction`) wrapped with `autoRefresh do` Extend or add a dedicated triage dashboard action wrapped with `autoRefresh do`.
2. Dashboard panels: Shows: open RequirementCandidates (count by status), triage queue (oldest open
- KPI row: open / in_review / accepted / rejected / deferred counts first), recent escalations (last 20), candidates by category breakdown.
- Triage queue: open candidates sorted oldest-first (title, widget, category, age) Live-updates on any candidate/triage state change.
- 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. **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" state_hub_task_id: "935de4d7-867f-49aa-bddf-6ff9435215de"
``` ```
1. **Integration tests** (`Test/`): Integration tests: AnnotationThread CRUD; escalation happy path; triage state
- AnnotationThread create + assign annotation transitions (valid + invalid); reviewer assignment; dashboard AutoRefresh
- Escalation: annotation → candidate, duplicate escalation blocked wrapper present. Consistency sync. Update `SCOPE.md` current state. Write
- Triage transitions: valid path (open → in_review → accepted); invalid transition rejected (422) `docs/phase2-summary.md`. Smoke test: create thread, escalate annotation, triage
- ReviewerAssignment: assign, reassign, my-queue filter candidate, confirm dashboard updates.
- 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. **Exit criteria:** All integration tests pass; `SCOPE.md` reflects Phase 2
completion; consistency check passes.
---
## 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.