Files
inter-hub/docs/ihp-ihf-mapping.md
tegwick 8b6ce5bbc8 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>
2026-03-27 02:07:13 +01:00

8.9 KiB

IHP ↔ IHF Capability Mapping

How IHP's specific capabilities serve the Interaction Hub Framework's requirements. Use this as a decision guide when implementing IHF modules.


Core Mapping

IHF Requirement IHP Capability Notes
Widget semantic identity (stable IDs) Id Widget newtype, Schema.sql UUIDs prevent FK mixups at compile time
Widget registry CRUD Standard IHP controller + AutoRoute Code generator scaffolds it in minutes
Widget envelope metadata JSONB columns + Haskell Value config, metadata, context_ref fields
Interaction event capture (append-only) Controller action + createRecord Add DB-level APPEND-only trigger for enforcement
Annotation threads Belongs-to relationships + fetchRelated annotations.parent_id for threading
Live dashboard (hub-level signals) AutoRefresh Zero client-side framework needed
Reactive annotation UI Server-Side Components or HTMX SSC for rich state; HTMX for simple append
Multi-tenant widget data isolation DataSync + PostgreSQL RLS ihp_user_id() in RLS policies
Governance ledger (decision records) Append-only table + HTMX decisions table; controllers append, never update
Actor attribution currentUserOrNothing + actor_type field Supports human/agent/automation attribution
Traceability chain FK relationships across tables Widget → InteractionEvent → Annotation → RequirementCandidate
Async processing (batch analysis) IHP Background Jobs RunJobs binary; jobs queued in Postgres
Reproducible deployment NixOS + deploy-to-nixos All server config version-controlled
AI-assisted distillation (Phase 5) Background Jobs + external API calls Job fetches annotation cluster, calls AI API, stores AgentProposal

Type Safety as Governance Infrastructure

IHP's type system is more than a developer convenience — it is governance infrastructure for IHF:

Widget identity integrity: Id WidgetId AnnotationId Hub. Cross-type ID confusion (a common source of traceability chain breakage) is a compile error, not a runtime bug.

Field existence enforcement: fill @'["widgetType", "hubId"] lists the fields bound from HTTP parameters. Adding or removing a field in Schema.sql propagates as a compile error to every controller that uses it — schema drift is caught immediately.

URL correctness: redirectTo ShowWidgetAction { widgetId = w.id } — if ShowWidgetAction is renamed or its fields change, every call site fails to compile. Broken governance links are impossible.

View exhaustiveness: case on widget status or annotation category in views will produce a GHC warning if a new constructor is added to the enum — ensuring governance views stay current with the data model.


Schema Design Recommendations for IHF Phase 1

-- Hubs: bounded domains of responsibility
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
);

-- Widget registry
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,          -- chart | form | table | action | panel | etc.
    capability_ref TEXT,                -- reference to hub capability
    view_context TEXT,                  -- logical location in the UI
    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
);

-- Widget version history
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)
);

-- Interaction events (append-only; never UPDATE or DELETE)
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,           -- viewed | clicked | submitted | commented | flagged_confusing | etc.
    actor_id UUID,
    actor_type TEXT NOT NULL DEFAULT 'user',   -- user | agent | automation | anonymous
    view_context_ref TEXT,
    metadata JSONB DEFAULT '{}' NOT NULL,
    occurred_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);

-- Annotations (structured comments on widgets)
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,   -- for threads
    body TEXT NOT NULL,
    category TEXT NOT NULL DEFAULT 'friction',   -- friction | defect | wish | policy_concern | doc_gap | trust | other
    actor_id UUID,
    actor_type TEXT NOT NULL DEFAULT 'user',
    widget_state_ref TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);

AutoRefresh for Hub Dashboards

The hub operator dashboard is the primary immediate value of IHF Phase 1. AutoRefresh delivers it with minimal complexity:

-- Web/Controller/Hubs.hs
action ShowHubAction { hubId } = autoRefresh do
    hub          <- fetch hubId
    widgets      <- query @Widget
                     |> filterWhere (#hubId, hubId)
                     |> orderByDesc #createdAt
                     |> fetch
    recentEvents <- query @InteractionEvent
                     |> filterWhere (#hubId, hubId)
                     |> orderByDesc #occurredAt
                     |> limit 50
                     |> fetch
    annotations  <- query @Annotation
                     |> filterWhere (#hubId, hubId)
                     |> orderByDesc #createdAt
                     |> limit 20
                     |> fetch
    render ShowView { .. }

Any insert into widgets, interaction_events, or annotations with this hub_id automatically re-renders the dashboard for all connected operators. No WebSocket plumbing needed on the server side beyond the autoRefresh wrapper.


HTMX for the Governance Ledger

The governance ledger should be append-only. HTMX's hypermedia pattern maps directly:

-- Append a decision record
action CreateDecisionAction { requirementId } = do
    let decision = newRecord @Decision
    decision
        |> fill @'["outcome", "rationale"]
        |> validateField #outcome nonEmpty
        |> validateField #rationale nonEmpty
        |> ifValid \case
            Left _ -> respondHtml errorFragment
            Right decision -> do
                decision <- createRecord decision
                -- Also update requirement status
                requirement <- fetch requirementId
                    >>= updateRecord . set #status "decided"
                respondHtml (renderDecisionCard decision)

The client receives a rendered <div class="decision-card"> fragment and appends it to the ledger list. No full page reload; no separate API contract; the server is the single source of truth.


Background Jobs for Async Distillation (Future — Phase 5)

When IHF Phase 5 (Agent-Assisted Distillation) is implemented, IHP's job system handles the async pipeline:

-- Application/Job/DistillAnnotationsJob.hs
instance Job DistillAnnotationsJob where
    perform DistillAnnotationsJob { widgetId } = do
        annotations <- query @Annotation
            |> filterWhere (#widgetId, widgetId)
            |> filterWhere (#requirementId, Nothing)  -- unprocessed
            |> fetch
        when (length annotations >= 3) do
            -- Call AI API for clustering + proposal draft
            proposal <- callAIDistillation annotations
            createRecord proposal
            -- Trigger AutoRefresh on governance views
            notifyTable "requirement_candidates"

Jobs are queued as Postgres records and processed by the RunJobs binary, which runs alongside the main app in production.


Deployment for IHF

IHF infrastructure can be fully declared in Config/nix/hosts/ and deployed with deploy-to-nixos. A minimal configuration.nix for Phase 1:

{ config, pkgs, ... }: {
  services.ihp = {
    enable = true;
    domain = "ihf.yourdomain.com";
    ihpPackage = (import ./ihf.nix).ihf;
    dbName = "ihf_production";
    sessionSecret = config.age.secrets.ihpSessionSecret.path;
  };

  services.nginx.enable = true;

  security.acme.defaults.email = "admin@yourdomain.com";
  security.acme.acceptTerms = true;
}

All secrets (session key, DB password) managed via agenix — encrypted in git, decrypted on the NixOS host at deploy time.