From c9aa004bf838b0927c7e0b09a93caf7703452c93 Mon Sep 17 00:00:00 2001 From: Bernd Worsch Date: Sun, 29 Mar 2026 21:53:33 +0000 Subject: [PATCH] chore: register Phase 8 workplan (IHUB-WP-0008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8 tasks: T01 schema (WidgetOwnership, HubRoutingRule, FederatedPolicyOverlay, StewardshipRole, ArchiveRecord) → T02 delegated ownership UI → T03 inter-hub routing engine → T04 federated policy overlays → T05 stewardship roles → T06 archival + lineage inspector → T07 federated governance dashboard → T08 gate. Co-Authored-By: Claude Sonnet 4.6 --- ...-0008-ihf-phase8-federated-hub-maturity.md | 440 ++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 workplans/IHUB-WP-0008-ihf-phase8-federated-hub-maturity.md diff --git a/workplans/IHUB-WP-0008-ihf-phase8-federated-hub-maturity.md b/workplans/IHUB-WP-0008-ihf-phase8-federated-hub-maturity.md new file mode 100644 index 0000000..34138d3 --- /dev/null +++ b/workplans/IHUB-WP-0008-ihf-phase8-federated-hub-maturity.md @@ -0,0 +1,440 @@ +--- +id: IHUB-WP-0008 +type: workplan +title: "IHF Phase 8 — Federated Hub Maturity" +domain: inter_hub +repo: inter-hub +status: todo +owner: custodian +topic_slug: inter_hub +created: "2026-03-29" +updated: "2026-03-29" +state_hub_workstream_id: "ea47db6f-9a3c-43bc-a3ea-bec17b6b01e7" +--- + +# IHF Phase 8 — Federated Hub Maturity + +## Goal + +Support mature multi-hub deployment in an AI factory context. Phase 7 +established cross-hub observability. Phase 8 introduces the governance +structures needed when multiple teams, hubs, and policies must coexist: +delegated widget ownership, requirement routing across hub boundaries, +org-wide federated policy overlays, named stewardship roles, and long-term +archival with full lineage inspection. + +## Background + +Phases 1–7 are complete. The IHF core is stable across widget governance, +interaction capture, structured feedback, decision ledger, outcome observation, +agent assistance, cross-framework adapters, and operational observability. + +The spec (§Phase 8) calls for: +- Delegated ownership +- Multi-team governance +- Inter-hub requirement routing +- Federated policy overlays +- Mature reporting and stewardship roles +- Long-term archival and lineage inspection + +Artifacts introduced: `WidgetOwnership`, `HubRoutingRule`, +`FederatedPolicyOverlay`, `StewardshipRole`, `ArchiveRecord`. + +Reference: `specs/InteractionHubFrameworkSpecification_v0.1.md` §Phase 8, +`docs/phase7-summary.md`, `docs/ihp-controllers-views-forms.md`. + +## Phase 8 Exit Criteria (from IHF spec §Phase 8) + +- The framework supports organisational scale +- Ownership and governance remain clear across hub boundaries +- Long-term platform memory is preserved + +## Data Artifacts Introduced (Phase 8) + +`WidgetOwnership`, `HubRoutingRule`, `FederatedPolicyOverlay`, +`StewardshipRole`, `ArchiveRecord` + +--- + +## Tasks + +### T01 — Schema: WidgetOwnership, HubRoutingRule, FederatedPolicyOverlay, StewardshipRole, ArchiveRecord + +```task +id: IHUB-WP-0008-T01 +status: todo +priority: high +state_hub_task_id: "5c5315b7-98ff-45dc-8eef-a5df83e18ea2" +``` + +Add Phase 8 tables to `Application/Schema.sql` and write migration: + +```sql +-- Explicit ownership record for a widget — who owns and who stewards it. +CREATE TABLE widget_ownerships ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, + widget_id UUID NOT NULL REFERENCES widgets(id), + owner_hub_id UUID NOT NULL REFERENCES hubs(id), + steward_hub_id UUID REFERENCES hubs(id), + -- null = same as owner hub + ownership_type TEXT NOT NULL DEFAULT 'local', + -- 'local' | 'delegated' | 'global' + effective_from TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + effective_until TIMESTAMP WITH TIME ZONE, + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL +); + +CREATE INDEX widget_ownerships_widget_id_idx ON widget_ownerships (widget_id); +CREATE INDEX widget_ownerships_owner_hub_idx ON widget_ownerships (owner_hub_id); +CREATE INDEX widget_ownerships_steward_hub_idx ON widget_ownerships (steward_hub_id); + +-- Rule that automatically routes a RequirementCandidate to another hub. +CREATE TABLE hub_routing_rules ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, + source_hub_id UUID NOT NULL REFERENCES hubs(id), + target_hub_id UUID NOT NULL REFERENCES hubs(id), + match_category TEXT, + -- null = match any category + match_widget_type TEXT, + -- null = match any widget type + priority INTEGER NOT NULL DEFAULT 0, + -- higher = evaluated first + status TEXT NOT NULL DEFAULT 'inactive', + -- 'active' | 'inactive' + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL +); + +CREATE INDEX hub_routing_rules_source_idx ON hub_routing_rules (source_hub_id); +CREATE INDEX hub_routing_rules_status_idx ON hub_routing_rules (status); + +-- Add routing destination to requirement candidates. +ALTER TABLE requirement_candidates + ADD COLUMN routed_to_hub_id UUID REFERENCES hubs(id); + +CREATE INDEX requirement_candidates_routed_hub_idx + ON requirement_candidates (routed_to_hub_id) + WHERE routed_to_hub_id IS NOT NULL; + +-- Org-wide policy overlay applied across selected hubs. +CREATE TABLE federated_policy_overlays ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, + title TEXT NOT NULL, + policy_text TEXT NOT NULL, + applies_to_hubs JSONB NOT NULL DEFAULT '[]', + -- [] means all hubs + enforced_from TIMESTAMP WITH TIME ZONE, + status TEXT NOT NULL DEFAULT 'draft', + -- 'draft' | 'active' | 'retired' + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL +); + +CREATE INDEX federated_policy_overlays_status_idx ON federated_policy_overlays (status); + +-- Named governance role assigned to a hub. +CREATE TABLE stewardship_roles ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, + hub_id UUID NOT NULL REFERENCES hubs(id), + role_name TEXT NOT NULL, + -- e.g. "Hub Lead", "Policy Steward", "Triage Owner" + assigned_to TEXT NOT NULL, + -- person name or identifier + granted_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + revoked_at TIMESTAMP WITH TIME ZONE, + notes TEXT +); + +CREATE INDEX stewardship_roles_hub_id_idx ON stewardship_roles (hub_id); +CREATE INDEX stewardship_roles_active_idx ON stewardship_roles (revoked_at) + WHERE revoked_at IS NULL; + +-- Long-term archival entry for any IHF artifact. +CREATE TABLE archive_records ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, + subject_type TEXT NOT NULL, + -- 'Widget' | 'Requirement' | 'DecisionRecord' | 'DeploymentRecord' | etc. + subject_id UUID NOT NULL, + archived_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + reason TEXT NOT NULL, + archived_by TEXT NOT NULL, + lineage_ref TEXT + -- e.g. external doc URL, git SHA, or ADR reference +); + +CREATE INDEX archive_records_subject_type_idx ON archive_records (subject_type); +CREATE INDEX archive_records_subject_id_idx ON archive_records (subject_id); + +-- Soft-archive flag on widgets. +ALTER TABLE widgets + ADD COLUMN is_archived BOOLEAN NOT NULL DEFAULT FALSE; + +CREATE INDEX widgets_is_archived_idx ON widgets (is_archived) + WHERE is_archived = TRUE; +``` + +**Exit criteria:** `migrate` runs cleanly; all Phase 8 types available in GHCi. + +--- + +### T02 — Delegated Ownership: WidgetOwnership registry and assignment UI + +```task +id: IHUB-WP-0008-T02 +status: todo +priority: high +state_hub_task_id: "4d12c8e2-7b8a-4da7-a37d-0663453a3e43" +``` + +1. Scaffold `WidgetOwnershipController`: + - `index`: table of all ownership records — widget name, owner hub, steward + hub (if different), type badge, effective range + - `new` / `create`: assign ownership to a widget + - `show`: full detail + - `edit` / `update`: change steward hub, ownership type, or effective_until; + no delete (audit artifact) +2. Validation: + - `widget_id`, `owner_hub_id`, `ownership_type` required + - `ownership_type` must be `local | delegated | global` + - `effective_until` must be after `effective_from` if set + - `delegated` type requires `steward_hub_id ≠ owner_hub_id` +3. Widget show page: ownership badge (owner hub name + type, colour-coded: + local=gray, delegated=blue, global=purple) +4. Hub show page: "Owned Widgets" and "Stewarded Widgets" sections (collapsed by + default, expandable) +5. Link `WidgetOwnershipController` from global nav as "Ownership" + +**Exit criteria:** Ownership records can be created and viewed; widget show page +renders the badge; hub show page lists owned/stewarded widgets. + +--- + +### T03 — Inter-Hub Requirement Routing: routing rules and cross-hub candidate forwarding + +```task +id: IHUB-WP-0008-T03 +status: todo +priority: high +state_hub_task_id: "54597bea-bd1f-41ab-bb50-f2f19dc45c01" +``` + +1. Scaffold `HubRoutingRulesController`: + - `index`: table of all routing rules — source → target, match criteria, status + - `new` / `create` + - `edit` / `update`: notes and match criteria + - `ActivateRoutingRuleAction` / `DeactivateRoutingRuleAction` + - No delete +2. Add `Application/Helper/RoutingEngine.hs` with `applyRoutingRules`: + - For a given `RequirementCandidate`, find active rules for its source hub + - Sort by `priority DESC` + - First matching rule (by `match_category` and/or `match_widget_type`) sets + `routed_to_hub_id` on the candidate + - Match is `null`-inclusive: a null `match_category` matches any category +3. Call `applyRoutingRules` in `RequirementCandidatesController`: + - After `createRecord` in `CreateRequirementCandidateAction` + - In `RouteNowAction { requirementCandidateId }` (manual re-evaluation) +4. Add `RoutedCandidatesAction { hubId }`: candidates with + `routed_to_hub_id = hubId`, from any source hub — shown in a dedicated view +5. Candidate show and triage views: show a "Routed to: HubName" badge if + `routed_to_hub_id` is set; show "Routed from: HubName" if the candidate + originated in a different hub +6. Link "Routing Rules" from global nav; link "Routed In" from hub Show page + +**Exit criteria:** A candidate created in a hub with a matching active rule +receives `routed_to_hub_id`; `RoutedCandidatesAction` shows it; manual +`RouteNowAction` re-evaluates. + +--- + +### T04 — Federated Policy Overlays: org-wide policies applied across all hubs + +```task +id: IHUB-WP-0008-T04 +status: todo +priority: high +state_hub_task_id: "df2fcdb1-657f-49d1-b340-79d4f55a9088" +``` + +1. Scaffold `FederatedPolicyOverlaysController`: + - `index`: table — title, status badge, scope (all hubs / N hubs), enforced from + - `show`: full policy text, `applies_to_hubs` list resolved to hub names, + linked decisions count + - `new` / `create` + - `edit` / `update`: draft only; activated overlays are read-only + - `ActivateFederatedPolicyAction` — sets `status=active`, records + `enforced_from=now()`; once active, no further edits + - `RetireFederatedPolicyAction` — sets `status=retired` +2. Add `PolicyComplianceDashboardAction` (global): + - For each active overlay: hub scope (all vs specific), count of + `DecisionRecord`s in-scope hubs that reference a `PolicyReference` + - "Coverage %" = decisions with at least one policy reference / total + decisions in scope +3. On `DecisionRecord` show page: list any active `FederatedPolicyOverlay` + records whose `applies_to_hubs` includes this decision's hub (or is `[]`) +4. Link "Policies" from global nav + +**Exit criteria:** Overlay activates; activated overlay cannot be edited; policy +compliance dashboard shows coverage metrics; decision show page lists applicable +overlays. + +--- + +### T05 — Stewardship Roles: named governance roles per hub + +```task +id: IHUB-WP-0008-T05 +status: todo +priority: medium +state_hub_task_id: "490f37e1-44b2-4667-8213-4498121aaa55" +``` + +1. Scaffold `StewardshipRolesController`: + - `index`: all roles across all hubs, grouped by hub — role name, assigned + to, granted at, revoked at (if any) + - `show` + - `new` / `create` + - `RevokeRoleAction { stewardshipRoleId }` — sets `revoked_at = now()` + - No delete, no edit (create a new record to replace) +2. Hub show page: "Active Stewards" section listing current active + `StewardshipRole` records for this hub (revoked_at IS NULL) +3. `DecisionRecord` show page: list stewardship roles active at `decided_at` for + the decision's hub — `granted_at ≤ decided_at AND (revoked_at IS NULL OR + revoked_at > decided_at)` +4. Extend `OperationalReviewBoardView` with a Panel 5: hubs with zero active + stewardship roles +5. Link "Stewards" from global nav + +**Exit criteria:** Roles can be granted and revoked; hub show page lists active +stewards; decision show page shows contextual stewards; ops board panel renders. + +--- + +### T06 — Archival and Lineage Inspection + +```task +id: IHUB-WP-0008-T06 +status: todo +priority: medium +state_hub_task_id: "4b59d882-b690-4e14-8460-614bd114ce7a" +``` + +1. Scaffold `ArchiveRecordsController`: + - `index`: all archive records — subject type, subject ID (linked if + applicable), archived at, reason, archived by + - `show`: full detail including `lineage_ref` +2. Add `ArchiveWidgetAction { widgetId }`: sets `widgets.is_archived = true`, + creates `ArchiveRecord`. Redirect to widget show. Archived widgets show a + "Archived" banner; excluded from hub widget counts and triage queries via + `filterWhere (#isArchived, False)`. +3. Add `LineageInspectorAction { widgetId }` (widget-scoped for Phase 8): + - Fetches the full traceability chain in order: + `Widget → InteractionEvents → Annotations → RequirementCandidates + → Requirements → DecisionRecords → DeploymentRecords → OutcomeSignals` + - Also includes any `ArchiveRecord` for the widget + - Read-only timeline view; each step shows count and link to list +4. Link "Lineage" from widget show page +5. Link "Archive" from global nav (`ArchiveRecordsAction`) + +**Exit criteria:** Widget can be archived; archive record created; `is_archived` +flag filters it from active queries; lineage inspector renders the full chain. + +--- + +### T07 — Federated Governance Dashboard: org-wide governance health + +```task +id: IHUB-WP-0008-T07 +status: todo +priority: medium +state_hub_task_id: "0c2f6b98-41a5-4876-8bcc-07af08acaf77" +``` + +1. Add `FederatedGovernanceDashboardAction` (global, `autoRefresh`): + - **Panel 1 — Ownership coverage**: total widgets / widgets with ownership + record / breakdown by type (local / delegated / global); percentage bar + - **Panel 2 — Routing activity**: active routing rules count; candidates + routed cross-hub in last 30 days; top 5 source → target hub pairs + - **Panel 3 — Policy compliance**: active overlay count; hubs in scope; + decisions referencing a federated policy / total decisions in scope (%) + - **Panel 4 — Stewardship coverage**: hubs with ≥1 active steward / total + hubs; list of hubs with no stewards + - **Panel 5 — Archive activity**: artifact counts archived in last 90 days, + grouped by `subject_type` +2. Link from global nav as "Federation" +3. Link from Operational Review Board as a "Federated Governance →" shortcut + +**Exit criteria:** Dashboard renders all five panels; live-updates on DB change; +all counts are correct against test fixtures. + +--- + +### T08 — Phase 8 gate: tests, consistency, docs + +```task +id: IHUB-WP-0008-T08 +status: todo +priority: high +state_hub_task_id: "422cae8f-5dc6-4393-b78a-77169b00da8a" +``` + +1. **Integration tests** (`Test/`): + - `WidgetOwnership` create + type transitions (local → delegated → global) + - `HubRoutingRule` create + activate + candidate routing (`applyRoutingRules` + sets `routed_to_hub_id` for matching candidate) + - `FederatedPolicyOverlay` create + activate (edit blocked after activation) + - `StewardshipRole` create + revoke; contextual query at a past timestamp + - `ArchiveRecord` create; `is_archived = true` excludes widget from active + queries; lineage fetch returns full chain + - `FederatedGovernanceDashboard` compiles, fetches hubs, returns correct + ownership coverage count +2. **Consistency sync** via State Hub MCP: + `check_repo_consistency(repo_slug="inter-hub", fix=True)` +3. **Documentation updates:** + - Update `SCOPE.md` current state: Phase 8 complete + - Write `docs/phase8-summary.md`: ownership model, routing engine, policy + overlay immutability, stewardship audit pattern, archival soft-delete, + lineage chain, known limitations +4. **Smoke test checklist:** + - Assign `delegated` ownership to a widget; verify badge on show page + - Create + activate routing rule; create a candidate; verify `routed_to_hub_id` + - Create + activate federated policy overlay; attempt to edit (expect blocked) + - Grant and revoke a stewardship role; verify ops board Panel 5 + - Archive a widget; verify excluded from hub widget list; view lineage chain + - Open Federated Governance Dashboard; confirm all five panels + +**Exit criteria:** All tests pass; consistency sync reports no errors; smoke +test completed; SCOPE.md updated. + +--- + +## Phase 8 Dependencies + +- Phases 1–7 schema stable +- `widget_ownerships` requires widgets and hubs (T01 before T02) +- `hub_routing_rules` + `routed_to_hub_id` on candidates requires hubs and + requirement_candidates (T01 before T03) +- `federated_policy_overlays` is independent but compliance dashboard reads + decisions (T04 after T01) +- `stewardship_roles` requires hubs (T01 before T05) +- `archive_records` + `is_archived` on widgets (T01 before T06) +- Federated Governance Dashboard aggregates all Phase 8 data (T02–T06 before T07) +- All feature tasks (T01–T07) before gate (T08) + +## Notes + +- **Activated policy overlays are immutable.** Create a new overlay to supersede + — old overlays remain readable for audit. Same pattern as Phase 6 contracts. +- **Ownership records are never deleted.** `effective_until` signals expiry. + The latest active record (effective_from ≤ now, effective_until IS NULL or + > now) is the authoritative ownership. +- **Routing is additive.** Multiple rules can match; only the highest-priority + active rule fires. Re-running `RouteNowAction` re-evaluates and may update + `routed_to_hub_id`. +- **Archival is soft-delete.** `is_archived = true` on widgets; the widget row + is preserved. All related records remain intact. Lineage inspection works on + archived widgets. +- **Stewardship is a point-in-time record.** Querying stewards "at time T" uses + `granted_at ≤ T AND (revoked_at IS NULL OR revoked_at > T)`.