generated from coulomb/repo-seed
docs: add specification, reference docs, workplan, and agent guidance
Adds all Phase 0 content that was created but never committed: - CLAUDE.md and SCOPE.md — agent and developer orientation - specs/TailwindForInteractionHubs_v0.2.md — IHF Tailwind coding guide - docs/ — five IHP v1.5 reference guides (overview, data, controllers, realtime, ihf-mapping) - workplans/IHUB-WP-0001 — Phase 1 implementation plan (12 tasks, state-hub synced) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
418
workplans/IHUB-WP-0001-ihf-phase1-minimal-interaction-core.md
Normal file
418
workplans/IHUB-WP-0001-ihf-phase1-minimal-interaction-core.md
Normal file
@@ -0,0 +1,418 @@
|
||||
---
|
||||
id: IHUB-WP-0001
|
||||
type: workplan
|
||||
title: "IHF Phase 1 — Minimal Interaction Core"
|
||||
domain: custodian
|
||||
repo: inter-hub
|
||||
status: active
|
||||
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: todo
|
||||
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 10–15 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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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: todo
|
||||
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 T04–T07)
|
||||
- Auth before traceability view (T10 before T11)
|
||||
- All feature tasks (T01–T11) 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 T04–T07 scaffolding, then customize. It handles the `Types.hs` / `Routes.hs` / `FrontController.hs` wiring automatically.
|
||||
Reference in New Issue
Block a user