Files
inter-hub/workplans/IHUB-WP-0001-ihf-phase1-minimal-interaction-core.md
Bernd Worsch 2031a93f2e
Some checks failed
Test / test (push) Has been cancelled
chore: claim workplan under inter_hub domain
Register inter-hub in the State Hub as its own domain (inter_hub),
update repo host path, wire domain goal / repo goal / workstream.
Workplan domain corrected from custodian → inter_hub.

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

419 lines
15 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-0001
type: workplan
title: "IHF Phase 1 — Minimal Interaction Core"
domain: inter_hub
repo: inter-hub
status: done
owner: custodian
topic_slug: custodian
created: "2026-03-27"
updated: "2026-03-27"
state_hub_workstream_id: "4733dbde-bdcf-4e00-b9b8-749f92e50cae"
---
# IHF Phase 1 — Minimal Interaction Core
## Goal
Implement the minimum viable governed interaction substrate for the Interaction Hub
Framework: a working widget registry, interaction event capture, annotation system,
and hub-level operator dashboard. This delivers Phase 1 of the IHF specification
(`specs/InteractionHubFrameworkSpecification_v0.1.md`).
## Background
Phase 0 (specification foundation) is complete. The IHF spec defines 8 phases;
Phase 1 establishes the semantic core that all subsequent phases build on.
**Technology stack:** IHP v1.5 (Haskell, Nix), PostgreSQL, AutoRefresh (live
dashboards), HTMX (governance actions), standard IHP forms (widget/annotation CRUD).
Reference: `docs/ihp-overview.md`, `docs/ihp-data-and-queries.md`,
`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`,
`docs/ihp-ihf-mapping.md`.
## Phase 1 Exit Criteria (from IHF spec §14 Phase 1)
- Widgets can be addressed and commented on reliably
- Interaction data is captured with actor attribution and view context
- Hub-level inspection of interaction signals is possible
## Data Artifacts Introduced (Phase 1)
`Hub`, `Widget`, `WidgetVersion`, `InteractionEvent`, `Annotation`, `CapabilityReference`, `ViewContext`
---
## Tasks
### T01 — IHP project bootstrap
```task
id: IHUB-WP-0001-T01
status: done
priority: high
state_hub_task_id: "e9e83628-d485-4163-9467-0d161f6274f3"
```
Set up the IHP project skeleton for inter-hub:
1. Install Determinate Nix and `ihp-new` if not already present
2. Run `ihp-new ihf` inside `/home/worsch/inter-hub/` (or initialise in-place)
3. Verify `devenv up` starts cleanly (app on `:8000`, IDE on `:8001`, Postgres managed by Nix)
4. Commit the baseline scaffold
5. Note first-startup time (expect 1015 min for Nix cache population)
**Exit criteria:** `devenv up` succeeds; `http://localhost:8000` returns the IHP welcome page.
---
### T02 — Schema design: Hub, Widget, WidgetVersion
```task
id: IHUB-WP-0001-T02
status: done
priority: high
state_hub_task_id: "e7254445-1375-44c3-9c59-111215b70692"
```
Define the widget registry tables in `Application/Schema.sql`:
```sql
CREATE TABLE hubs (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
domain TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE TABLE widgets (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE RESTRICT,
name TEXT NOT NULL,
widget_type TEXT NOT NULL,
capability_ref TEXT,
view_context TEXT,
policy_scope TEXT NOT NULL DEFAULT 'internal',
status TEXT NOT NULL DEFAULT 'active',
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE TABLE widget_versions (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE,
version INTEGER NOT NULL,
schema_snapshot JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
UNIQUE (widget_id, version)
);
```
- Write corresponding migration file in `Application/Migration/`
- Verify Haskell types are generated correctly (IHP auto-generates on save)
- Seed a dev `Hub` record for local development
**Exit criteria:** `migrate` runs cleanly; `Hub`, `Widget`, `WidgetVersion` types available in GHCi.
---
### T03 — Schema design: InteractionEvent and Annotation
```task
id: IHUB-WP-0001-T03
status: done
priority: high
state_hub_task_id: "dac18955-7b2f-464f-97eb-0733c9163088"
```
Define the capture tables in `Application/Schema.sql`:
```sql
CREATE TABLE interaction_events (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
actor_id UUID,
actor_type TEXT NOT NULL DEFAULT 'user',
view_context_ref TEXT,
metadata JSONB DEFAULT '{}' NOT NULL,
occurred_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE INDEX interaction_events_widget_id_idx ON interaction_events (widget_id);
CREATE INDEX interaction_events_occurred_at_idx ON interaction_events (occurred_at DESC);
CREATE TABLE annotations (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE,
parent_id UUID REFERENCES annotations(id) ON DELETE CASCADE,
body TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'friction',
actor_id UUID,
actor_type TEXT NOT NULL DEFAULT 'user',
widget_state_ref TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE INDEX annotations_widget_id_idx ON annotations (widget_id);
```
- Write migration file
- `interaction_events` is append-only: add a PostgreSQL trigger or application-level guard preventing UPDATE/DELETE
- Valid `category` values: `friction`, `defect`, `wish`, `policy_concern`, `doc_gap`, `trust`, `other`
- Valid `actor_type` values: `user`, `agent`, `automation`, `anonymous`
**Exit criteria:** Migration runs cleanly; types generated; append-only guard in place.
---
### T04 — Hub controller and views (CRUD)
```task
id: IHUB-WP-0001-T04
status: done
priority: high
state_hub_task_id: "20517418-85c9-4335-a445-dbbf99a81ae5"
```
Scaffold Hub management:
1. Use IHP Code Generator (`localhost:8001/Generators`) to scaffold `HubsController`
2. Implement index, show, new, create, edit, update, delete actions
3. Index view: list of hubs with slug, domain, widget count
4. Show view: hub details + list of widgets (with event counts)
**Exit criteria:** Hubs can be created, listed, viewed, edited, and deleted via the web UI.
---
### T05 — Widget Registry controller and views (CRUD)
```task
id: IHUB-WP-0001-T05
status: done
priority: high
state_hub_task_id: "262bfdb0-896c-4873-981f-36ea865b5dfe"
```
Scaffold Widget management:
1. Scaffold `WidgetsController`
2. Implement index, show, new, create, edit, update actions (no delete — widgets are deprecated, not deleted)
3. `CreateWidgetAction`: on create, also insert a `WidgetVersion` record with `version=1` and a JSON snapshot of the widget
4. `UpdateWidgetAction`: increment `version`, insert new `WidgetVersion` record
5. Index view: table of widgets with hub, type, status, version, event count
6. Show view: widget detail + version history + recent interaction events + annotations
7. Form: `name`, `widget_type` (select), `hubId` (select), `capabilityRef`, `viewContext`, `policyScope` (select: internal/hub/public), `status`
**Exit criteria:** Widgets can be registered, listed, and viewed. Version history is tracked on every update.
---
### T06 — Interaction Event capture
```task
id: IHUB-WP-0001-T06
status: done
priority: high
state_hub_task_id: "3a48509e-9014-43d1-a244-21d7c322d8cc"
```
Implement interaction event capture:
1. `POST /widgets/:widgetId/events``CreateInteractionEventAction { widgetId }`
2. Bind: `event_type`, `actor_id` (optional), `actor_type`, `view_context_ref`, `metadata` (JSON)
3. Validate: `event_type` must be non-empty and in the canonical list (viewed, clicked, submitted, abandoned, retried, failed, commented, flagged_confusing, flagged_helpful, blocked_by_policy, escalated, accepted_recommendation, rejected_recommendation)
4. Populate `actor_id` / `actor_type` from `currentUserOrNothing` when the actor is authenticated
5. Respond with JSON `{ id, widget_id, event_type, occurred_at }` for programmatic clients
6. No HTML view needed for this action — it's a capture endpoint
**Exit criteria:** `POST` to the capture endpoint creates an `InteractionEvent` record with correct actor attribution; unknown `event_type` values are rejected with 422.
---
### T07 — Annotation controller
```task
id: IHUB-WP-0001-T07
status: done
priority: high
state_hub_task_id: "1cc61933-46cd-46d1-b79a-05a8b40cd23b"
```
Implement annotation CRUD:
1. Scaffold `AnnotationsController` scoped to a widget: `/widgets/:widgetId/annotations/`
2. `IndexAnnotationsAction { widgetId }` — list annotations, threaded by `parent_id`
3. `CreateAnnotationAction { widgetId }` — create annotation, auto-set `actor_id`/`actor_type` from session
4. Form: `body` (textarea), `category` (select), optional `parentId` (for replies), `widgetStateRef`
5. Validate: `body` non-empty; `category` in valid set
6. List view: threaded annotation tree (root annotations + replies indented)
7. No edit/delete (append-only); add a "retract" flag if needed (`retracted_at TIMESTAMP`)
**Exit criteria:** Annotations can be created and listed per widget with threading. Actor attribution is automatic for logged-in users.
---
### T08 — Widget Envelope convention
```task
id: IHUB-WP-0001-T08
status: done
priority: medium
state_hub_task_id: "d2dfbdf6-fe66-4478-afeb-7ea3f05bea2b"
```
Establish the Widget Envelope as a reusable HSX helper:
1. Create `Application/Helper/View.hs` function `widgetEnvelope`:
```haskell
widgetEnvelope :: Widget -> Html -> Html
widgetEnvelope widget inner = [hsx|
<div
class="ihf-widget"
data-widget-id={tshow widget.id}
data-widget-type={widget.widgetType}
data-hub-id={tshow widget.hubId}
data-capability-ref={fromMaybe "" widget.capabilityRef}
data-view-context={fromMaybe "" widget.viewContext}
data-policy-scope={widget.policyScope}
data-widget-version={tshow widget.version}
>
{inner}
<div class="ihf-widget-controls">
<a href={pathTo WidgetAnnotationsAction { widgetId = widget.id }}
class="ihf-annotate-btn">Annotate</a>
</div>
</div>
|]
```
2. Document the convention in `docs/widget-envelope-convention.md`
3. Demonstrate use in the Hub dashboard view by wrapping at least one widget card
**Exit criteria:** `widgetEnvelope` renders the correct `data-*` attributes; the annotate link is functional.
---
### T09 — Hub operator dashboard (AutoRefresh)
```task
id: IHUB-WP-0001-T09
status: done
priority: high
state_hub_task_id: "b0ca9f93-cd64-421f-a426-999f35db148f"
```
Implement the live hub operator dashboard:
1. `ShowHubAction` wrapped with `autoRefresh do`
2. Dashboard shows:
- Widget count by type and status
- Recent interaction events (last 50, across all hub widgets)
- Recent annotations (last 20, across all hub widgets)
- Per-widget event count bar (simple table or list)
3. Layout must include `{autoRefreshMeta}`, `morphdom.js`, `ihp-auto-refresh.js`
4. Test: open dashboard in two browser tabs; insert an event via `curl` → both tabs update within ~1s
**Exit criteria:** Dashboard auto-updates on new events/annotations without page reload. AutoRefresh diff is confirmed in browser DevTools (WebSocket frames visible).
---
### T10 — Authentication and actor attribution
```task
id: IHUB-WP-0001-T10
status: done
priority: medium
state_hub_task_id: "8ef87232-cb0d-4948-9bca-849048dd82c2"
```
Wire up IHP session auth for the admin/governance users:
1. Add `users` table to `Schema.sql`: `id`, `email`, `password_hash`, `locked_at`, `failed_login_attempts`, `name`
2. Configure `initAuthentication @User` in `FrontController`
3. Mount `SessionsController`
4. Add `beforeAction = ensureIsUser` to `HubsController` and `WidgetsController`
5. Update `CreateInteractionEventAction` and `CreateAnnotationAction` to read `currentUserOrNothing` and set `actor_id`/`actor_type` accordingly
6. Seed one admin user for local development (use `hash-password` CLI)
**Exit criteria:** Unauthenticated access to hubs/widgets redirects to login. Annotations and events created by logged-in users carry the correct `actor_id`.
---
### T11 — Manual traceability view: Widget → Annotations
```task
id: IHUB-WP-0001-T11
status: done
priority: medium
state_hub_task_id: "b342d44c-ca41-4373-a55d-c7dcc5121f4a"
```
Implement the traceability entry point (first link in the IHF traceability chain):
1. Widget show page (`ShowWidgetAction`) aggregates:
- Full annotation thread (threaded, with actor, category, timestamp)
- Interaction event history (paginated, 20 per page)
- Widget version history
2. Add a summary KPI row: total events, total annotations, annotation breakdown by category
3. Link to parent hub from widget detail (breadcrumb: Hub > Widget)
This is the Phase 1 terminal traceability view: Widget → InteractionEvents + Annotations.
**Exit criteria:** The widget show page presents a complete picture of all interaction signals and annotations for a widget, linked back to the hub.
---
### T12 — Phase 1 gate: tests, consistency, and documentation
```task
id: IHUB-WP-0001-T12
status: done
priority: high
state_hub_task_id: "ae5a8713-27ba-445b-a29f-822b5d0acf5a"
```
Gate tasks before Phase 1 is marked complete:
1. **Integration tests** (`Test/`):
- Widget CRUD happy path
- Event capture with and without authenticated user
- Annotation create + list + threading
- Validation rejection (empty body, invalid category, invalid event_type)
- AutoRefresh: verify `autoRefresh` wrapper is present on dashboard action
2. **Consistency sync:**
```bash
cd ~/the-custodian && make fix-consistency REPO=inter-hub
```
3. **Documentation updates:**
- Update `SCOPE.md` current state section: Phase 1 complete
- Write brief `docs/phase1-summary.md`: what was built, known limitations, Phase 2 readiness
4. **Smoke test checklist:**
- `devenv up` → clean start
- Create a hub, create 3 widgets, capture events via API, annotate via UI
- Dashboard auto-updates visible
- All tests pass
**Exit criteria:** All tests pass; consistency sync reports no errors; smoke test completed.
---
## Phase 1 Dependencies
- IHP v1.5 installed via Nix (T01)
- Schema stabilized before controller scaffolding (T02/T03 before T04T07)
- Auth before traceability view (T10 before T11)
- All feature tasks (T01T11) before gate (T12)
## Notes
- **No DataSync in Phase 1.** AutoRefresh is sufficient for the operator dashboard. DataSync (with RLS) is Phase 2 work for widget embeds.
- **No requirement candidates or decision records in Phase 1.** Those are Phase 2 (Structured Feedback and Triage) and Phase 3 (Governance and Decision Linkage).
- **Append-only events:** the PostgreSQL trigger on `interaction_events` (T03) is critical — enforce it before wiring the capture endpoint.
- **IHP Code Generator:** use it aggressively for T04T07 scaffolding, then customize. It handles the `Types.hs` / `Routes.hs` / `FrontController.hs` wiring automatically.