From b1d1f5066b0ca2968dfda8d20032d64a1fc6086d Mon Sep 17 00:00:00 2001 From: Bernd Worsch Date: Sun, 29 Mar 2026 21:27:10 +0000 Subject: [PATCH] =?UTF-8?q?feat(P6):=20IHF=20Phase=206=20complete=20?= =?UTF-8?q?=E2=80=94=20agent-assisted=20distillation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T08 gate: integration tests for all Phase 6 artifacts (EnvelopeEmissionContract, InteractionReportingContract, WidgetAdapterSpec, adapter assignment, dashboard coverage logic); SCOPE.md updated to Phase 6 complete; docs/phase6-summary.md written (contract model, adapter pattern, known limitations, Phase 7 readiness); workplan IHUB-WP-0006 marked done. Co-Authored-By: Claude Sonnet 4.6 --- SCOPE.md | 6 +- Test/Integration.hs | 114 ++++++++++++++++++ docs/phase6-summary.md | 97 +++++++++++++++ ...hf-phase6-cross-framework-ui-adaptation.md | 6 +- 4 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 docs/phase6-summary.md diff --git a/SCOPE.md b/SCOPE.md index 2fddbdc..f176b1a 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -65,9 +65,9 @@ IHF treats every meaningful UI element as a **governed interaction artifact** ra ## Current State -- Status: Phase 5 complete — agent-assisted distillation and suggestion implemented -- Implementation: Phase 0 complete (specification); Phase 1 complete (widget registry, event capture, annotations, hub dashboard, auth); Phase 2 complete (annotation severity, annotation threads, requirement candidates, triage lifecycle, reviewer assignment, triage dashboard); Phase 3 complete (requirement promotion, decision records, policy references, implementation change references, governance dashboard); Phase 4 complete (deployment records, outcome signals, pre/post comparison, regression detection, change evaluation, recurrence tracking, antifragility dashboard); Phase 5 complete (agent proposals, review records, confidence annotations, cluster summarization, requirement drafting, duplicate detection, policy sensitivity, implementation proposals, agent audit dashboard) -- Stability: core artifact model and schema are stable; Phase 5 data model (AgentProposal, AgentReviewRecord, ConfidenceAnnotation) is additive; all AI outputs are attributed and reviewer-controlled +- Status: Phase 6 complete — cross-framework UI adaptation layer implemented +- Implementation: Phase 0 complete (specification); Phase 1 complete (widget registry, event capture, annotations, hub dashboard, auth); Phase 2 complete (annotation severity, annotation threads, requirement candidates, triage lifecycle, reviewer assignment, triage dashboard); Phase 3 complete (requirement promotion, decision records, policy references, implementation change references, governance dashboard); Phase 4 complete (deployment records, outcome signals, pre/post comparison, regression detection, change evaluation, recurrence tracking, antifragility dashboard); Phase 5 complete (agent proposals, review records, confidence annotations, cluster summarization, requirement drafting, duplicate detection, policy sensitivity, implementation proposals, agent audit dashboard); Phase 6 complete (EnvelopeEmissionContract, InteractionReportingContract, WidgetAdapterSpec, REST API for cross-framework event submission, annotation launcher JS, React adapter, adapter compatibility dashboard) +- Stability: core artifact model and schema are stable; Phase 6 contracts are immutable once active; native IHP widgets unaffected (adapter_spec_id nullable); JS adapters are thin ESM modules with no build toolchain requirement - Usage: reference implementation running on IHP v1.5 + PostgreSQL; `devenv up` to start --- diff --git a/Test/Integration.hs b/Test/Integration.hs index 113b2d5..4b516d6 100644 --- a/Test/Integration.hs +++ b/Test/Integration.hs @@ -1090,3 +1090,117 @@ main = do length pending `shouldBe` 1 length accepted `shouldBe` 1 deleteRecord hub + + -- ---------------------------------------------------------------- + -- Phase 6 — Cross-Framework UI Adaptation Layer + -- ---------------------------------------------------------------- + + describe "EnvelopeEmissionContract" do + it "can create and fetch with required attributes" do + contract <- newRecord @EnvelopeEmissionContract + |> set #contractVersion "test-1.0" + |> set #requiredAttributes (toJSON (["data-widget-id","data-hub-id"] :: [Text])) + |> set #optionalAttributes (toJSON ([] :: [Text])) + |> set #validationRules (toJSON (object [])) + |> set #status "active" + |> createRecord + contract.contractVersion `shouldBe` "test-1.0" + contract.status `shouldBe` "active" + fetched <- fetch contract.id + fetched.requiredAttributes `shouldBe` contract.requiredAttributes + deleteRecord contract + + describe "InteractionReportingContract" do + it "can create and fetch" do + rc <- newRecord @InteractionReportingContract + |> set #contractVersion "test-1.0" + |> set #endpointPath "/api/v1/interaction-events" + |> set #acceptedEventTypes (toJSON (["clicked","viewed"] :: [Text])) + |> set #requiredFields (toJSON (["widget_id","hub_id"] :: [Text])) + |> set #authScheme "bearer" + |> set #status "active" + |> createRecord + rc.endpointPath `shouldBe` "/api/v1/interaction-events" + rc.authScheme `shouldBe` "bearer" + fetched <- fetch rc.id + fetched.contractVersion `shouldBe` "test-1.0" + deleteRecord rc + + describe "WidgetAdapterSpec" do + it "can create with draft status and transition to active" do + ec <- newRecord @EnvelopeEmissionContract + |> set #contractVersion "test-env-1.0" + |> set #requiredAttributes (toJSON ([] :: [Text])) + |> set #status "active" + |> createRecord + rc <- newRecord @InteractionReportingContract + |> set #contractVersion "test-rep-1.0" + |> set #endpointPath "/api/v1/interaction-events" + |> set #acceptedEventTypes (toJSON ([] :: [Text])) + |> set #requiredFields (toJSON ([] :: [Text])) + |> set #status "active" + |> createRecord + spec <- newRecord @WidgetAdapterSpec + |> set #name "test-react-18" + |> set #framework "react" + |> set #version "1.0" + |> set #envelopeContractId (Just ec.id) + |> set #reportingContractId (Just rc.id) + |> set #status "draft" + |> createRecord + spec.status `shouldBe` "draft" + spec.framework `shouldBe` "react" + -- Transition to active + spec |> set #status "active" |> updateRecord + fetched <- fetch spec.id + fetched.status `shouldBe` "active" + deleteRecord spec + deleteRecord ec + deleteRecord rc + + describe "Widget adapter assignment" do + it "widget can be assigned to an adapter spec and badge resolves" do + hub <- newRecord @Hub |> set #name "AdapterHub" |> createRecord + spec <- newRecord @WidgetAdapterSpec + |> set #name "badge-test-adapter" |> set #framework "vue" + |> set #version "1.0" |> set #status "active" + |> createRecord + widget <- newRecord @Widget + |> set #hubId hub.id + |> set #name "AdapterWidget" + |> set #widgetType "chart" + |> set #adapterSpecId (Just spec.id) + |> createRecord + widget.adapterSpecId `shouldBe` Just spec.id + mSpec <- case widget.adapterSpecId of + Nothing -> pure Nothing + Just sid -> fetchOneOrNothing sid + fmap (.name) mSpec `shouldBe` Just "badge-test-adapter" + deleteRecord widget + deleteRecord spec + deleteRecord hub + + describe "Adapter compatibility dashboard data" do + it "correctly categorises native vs adapter-backed widgets" do + hub <- newRecord @Hub |> set #name "CompatHub" |> createRecord + spec <- newRecord @WidgetAdapterSpec + |> set #name "compat-test" |> set #framework "react" + |> set #version "1.0" |> set #status "active" + |> createRecord + w1 <- newRecord @Widget + |> set #hubId hub.id |> set #name "Native" + |> set #widgetType "chart" + |> createRecord + w2 <- newRecord @Widget + |> set #hubId hub.id |> set #name "Adapted" + |> set #widgetType "form" |> set #adapterSpecId (Just spec.id) + |> createRecord + hubWidgets <- query @Widget |> filterWhere (#hubId, hub.id) |> fetch + let adapted = length (filter (\w -> isJust w.adapterSpecId) hubWidgets) + native = length (filter (\w -> isNothing w.adapterSpecId) hubWidgets) + adapted `shouldBe` 1 + native `shouldBe` 1 + deleteRecord w1 + deleteRecord w2 + deleteRecord spec + deleteRecord hub diff --git a/docs/phase6-summary.md b/docs/phase6-summary.md new file mode 100644 index 0000000..5107990 --- /dev/null +++ b/docs/phase6-summary.md @@ -0,0 +1,97 @@ +# IHF Phase 6 Summary — Cross-Framework UI Adaptation Layer + +## What Was Built + +Phase 6 ensures that widget identity, interaction capture, and annotation capability are preserved when UI components are authored outside of IHP HSX — React, Vue, or any JS-based component — without bypassing the IHF core. + +### Contract Artifacts + +**EnvelopeEmissionContract** (`envelope_emission_contracts`) +- Formalises the widget envelope as a versioned, immutable contract +- v1.0 seed: required `["data-widget-id", "data-view-context", "data-hub-id"]`, optional `["data-policy-scope", "data-widget-version"]` +- `widgetEnvelope` helper updated to validate required attributes at render time (non-crashing warning) +- Read-only controller + index/show pages under "Contracts" nav + +**InteractionReportingContract** (`interaction_reporting_contracts`) +- Standardised REST interface contract for external event and annotation submission +- v1.0 seed: endpoint `/api/v1/interaction-events`, accepted event types `["clicked","viewed","submitted","dismissed","errored"]`, required fields `["widget_id","hub_id","event_type","occurred_at"]` +- Read-only controller + index/show pages + +**WidgetAdapterSpec** (`widget_adapter_specs`) +- Registry of supported UI framework adapters; links to envelope and reporting contracts +- Status lifecycle: `draft → active → deprecated` +- Full CRUD controller (no delete — audit artifact) +- Widgets gain an optional `adapter_spec_id` FK (null = native IHP widget) + +### REST API + +`POST /api/v1/interaction-events` — JSON body, Bearer token auth (per-hub `api_key`): +- Validates payload against active `InteractionReportingContract` +- Creates `InteractionEvent` record +- Returns `201 Created` with `{id, widget_id, event_type, occurred_at}` or `422` with errors +- Custom `CanRoute`/`HasPath` instances (not AutoRoute) for the `/api/v1/` path prefix + +### JavaScript Client + +**`static/js/ihf-annotation-launcher.js`** — vanilla JS IIFE (no framework dependency): +- Scans DOM for `[data-widget-id]` on `DOMContentLoaded` +- Injects inline "annotate" trigger adjacent to each enrolled element +- On click: opens an inline form (textarea + category select) and POSTs to existing `/widgets/:widgetId/annotations` endpoint +- Works in React-rendered pages where IHP does not own the DOM +- Feature-flagged via `IHP_ANNOTATION_LAUNCHER=true` env var (`AnnotationLauncherEnabled` typed config) + +**`static/js/ihf-react-adapter.js`** — ESM module for React 18+: +- `useWidgetEnvelope(widgetId, hubId, viewContext)` — returns `data-*` props conforming to envelope v1.0 +- `withWidgetEnvelope(WrappedComponent, widgetId, hubId, viewContext)` — HOC wrapping root element +- `useInteractionReporter(widgetId, hubId)` — returns `reportEvent(type)` POSTing to `/api/v1/interaction-events` + +**`static/ihf-react-test.html`** — static test fixture with CDN React 18, a React-rendered widget, and an IHP-rendered widget on the same page, both enrolled with the annotation launcher. + +### Dashboards and Navigation + +**Adapter Compatibility Dashboard** (`AdapterCompatibilityDashboardAction { hubId }`): +- Panel 1: Adapter spec counts by status (active / draft / deprecated) +- Panel 2: Widget coverage — native IHP vs adapter-backed with percentage bar and per-spec breakdown +- Panel 3: Active contract versions in use (envelope + reporting) +- Panel 4: Unassigned widgets (no `adapter_spec_id`) +- Panel 5: Active adapter specs table with widget counts +- `autoRefresh` — live-updates on any DB change +- Linked from hub Show page and global nav ("Adapters") + +Widget show page renders an adapter badge (purple, links to spec) when `adapter_spec_id` is set. + +## Contract Model + +Contracts are **immutable once active**. A new version supersedes the old; old versions remain readable for audit. The status lifecycle is: + +``` +draft → active → superseded (EnvelopeEmissionContract, InteractionReportingContract) +draft → active → deprecated (WidgetAdapterSpec) +``` + +This prevents retroactive mutation of the rules that governed historical interaction events. + +## Adapter Pattern + +Any UI technology participates by honouring three obligations: + +1. **Envelope** — root DOM element carries `data-widget-id`, `data-view-context`, `data-hub-id` +2. **Event reporting** — interactions submitted to `POST /api/v1/interaction-events` with bearer auth +3. **Annotation** — annotation launcher picks up `data-widget-id` automatically; no adapter-specific code required + +Native IHP widgets are unaffected. `adapter_spec_id` is nullable. The `widgetEnvelope` helper continues to work unchanged. + +## Known Limitations + +- **No local JS build toolchain.** `ihf-react-adapter.js` is a plain ESM module; consumers must bundle it themselves (e.g. Vite, esbuild). Phase 6 does not add npm/webpack to the IHP project. +- **Bearer token scope.** The per-hub `api_key` is a simple secret stored in `hubs.api_key`. Phase 8 (federated) should replace this with OAuth or a proper token management service. +- **Annotation launcher requires no CSP violations.** The inline form approach works in permissive CSP environments; strict `script-src` policies may require nonce injection. +- **Cross-machine consistency checks.** The State Hub consistency checker runs on the server and cannot reach the local repo filesystem; C-01/C-07 errors in the check output are path false-positives for this deployment topology. + +## Phase 7 Readiness + +Phase 6 leaves the IHF core stable and extensible: +- All Phase 6 contracts are versioned and immutable — Phase 7 can introduce v1.1 contracts without breaking existing adapters +- Widget `adapter_spec_id` is nullable and indexed — adapter assignment is opt-in +- The REST API surface is defined by the `InteractionReportingContract` record — contract evolution is data-driven +- The JS adapter is a thin ESM module — framework-specific adapters for Vue, Web Components, etc. follow the same pattern as `ihf-react-adapter.js` diff --git a/workplans/IHUB-WP-0006-ihf-phase6-cross-framework-ui-adaptation.md b/workplans/IHUB-WP-0006-ihf-phase6-cross-framework-ui-adaptation.md index d98b78b..f01d374 100644 --- a/workplans/IHUB-WP-0006-ihf-phase6-cross-framework-ui-adaptation.md +++ b/workplans/IHUB-WP-0006-ihf-phase6-cross-framework-ui-adaptation.md @@ -4,7 +4,7 @@ type: workplan title: "IHF Phase 6 — Cross-Framework UI Adaptation Layer" domain: inter_hub repo: inter-hub -status: todo +status: done owner: custodian topic_slug: inter_hub created: "2026-03-29" @@ -280,7 +280,7 @@ picks up the React widget's `data-widget-id`; docs written. ```task id: IHUB-WP-0006-T07 -status: todo +status: done priority: medium state_hub_task_id: "dc8fa48a-7195-4410-a77e-717b53127c2e" ``` @@ -311,7 +311,7 @@ adapter changes; stale adapter detection works. ```task id: IHUB-WP-0006-T08 -status: todo +status: done priority: high state_hub_task_id: "90ea4814-7603-4016-be34-d41ae091f7e1" ```