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:
2026-03-27 02:07:13 +01:00
parent 75b88ee760
commit 8b6ce5bbc8
9 changed files with 2181 additions and 0 deletions

207
docs/ihp-ihf-mapping.md Normal file
View File

@@ -0,0 +1,207 @@
# 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 Widget``Id Annotation``Id 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
```sql
-- 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:
```haskell
-- 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:
```haskell
-- 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:
```haskell
-- 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:
```nix
{ 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.