# Widget Envelope Contract **Name:** widget-envelope **Version:** 1.0 **Date:** 2026-03-31 **Status:** Active **Layer:** Core **Immutable:** Yes — changes require v1.1 with backwards-compatible additions only --- ## Purpose The Widget Envelope is the metadata boundary attached to every rendered widget. It enables the IHF capture pipeline to attribute any DOM event to a governed widget without coupling to the widget's implementation. Any UI technology that emits the required `data-*` attributes on a DOM element is a first-class IHF participant. --- ## Required Attributes Every widget root element **MUST** carry all of the following: | Attribute | Format | Example | |---|---|---| | `data-widget-id` | UUID (RFC 4122) | `data-widget-id="550e8400-e29b-41d4-a716-446655440000"` | | `data-hub-id` | Registered hub slug (TEXT) | `data-hub-id="ops-hub"` | | `data-view-context` | Dot-separated path (TEXT) | `data-view-context="ops.incidents.list"` | | `data-widget-type` | Registered name from `widget_type_registry` | `data-widget-type="chart"` | --- ## Optional Attributes | Attribute | Format | Notes | |---|---|---| | `data-capability-ref` | TEXT | Links widget to a hub capability | | `data-policy-scope` | Registered name from `policy_scope_registry` | Defaults to `internal` if absent | | `data-widget-version` | Integer as string | Current widget version number | | `data-experiment-variant` | TEXT | A/B experiment identifier | | `data-requirements-thread-ref` | UUID | Links to an open AnnotationThread | --- ## Validation Rules 1. `data-widget-id` must be a valid UUID. Non-UUID values cause the envelope to be treated as malformed. 2. `data-hub-id` must match the `slug` of a registered hub in the `hubs` table. Unknown hub slugs are logged but do not block event capture. 3. `data-widget-type` must exist in `widget_type_registry` with `status = 'active'` at the time of capture. Events from deprecated types are accepted but flagged. 4. `data-view-context` has no format enforcement in v1.0 beyond non-empty. --- ## Failure Mode - **Missing required attribute**: the capture pipeline logs a `malformed_envelope` event with the missing attribute names in `metadata`. The original interaction event is **not** stored. This is fail-safe, not fail-loud — the UI does not crash. - **Unknown hub slug**: event is stored with `metadata.hub_warning = "unknown_hub_slug"`. - **Unregistered widget_type**: event is stored with `metadata.type_warning = "unregistered_widget_type"`. No 422 is returned to the frontend (retroactive registration is possible). --- ## Backwards Compatibility Rule v1.0 is frozen. New optional attributes may be added in v1.1 without breaking existing implementations. Removing any attribute, or making an optional attribute required, requires a new major version (v2.0) with a migration window. --- ## Implementation Reference - Schema enforcement: `widgets.widget_type` validated against `widget_type_registry` on create/update in `WidgetsController` - Client-side: `static/ihf-annotation-launcher.js` reads `data-widget-id` and `data-hub-id` from the nearest ancestor with these attributes - Server-side envelope rendering: `widgetEnvelope` HSX helper in `Application/Helper/View.hs`