Implements the final phase of the IHF v0.1 specification: - WidgetOwnership: delegated ownership registry (local/delegated/global), append-only audit artefacts, ownership badge on widget show page - HubRoutingRule + RoutingEngine: priority-ordered inter-hub routing engine; null-inclusive category/widget-type matching; RouteNowAction for manual re-evaluation; RoutedCandidates view per hub - FederatedPolicyOverlay: draft → active → retired lifecycle; activated overlays are immutable (same pattern as Phase 6 contracts); policy compliance dashboard with decision coverage metrics - StewardshipRole: named governance roles per hub; point-in-time revocation pattern; hub and ops-board integration - ArchiveRecord + is_archived: soft-delete on widgets; lineage inspector traces full traceability chain (Widget → Events → Annotations → Candidates → Requirements → Decisions → Deployments → Signals + ArchiveRecord) - FederatedGovernanceDashboard: 5-panel autoRefresh org-wide governance view (ownership coverage, routing activity, policy compliance, stewardship coverage, archive activity) Schema: widget_ownerships, hub_routing_rules, federated_policy_overlays, stewardship_roles, archive_records; ALTER widgets ADD is_archived; ALTER requirement_candidates ADD routed_to_hub_id Migration: 1743638400-ihf-phase8-federated-hub-maturity.sql Tests: Phase 8 integration tests appended to Test/Integration.hs Docs: docs/phase8-summary.md; SCOPE.md updated to Phase 8 complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| IHUB-WP-0008 | workplan | IHF Phase 8 — Federated Hub Maturity | inter_hub | inter-hub | done | custodian | inter_hub | 2026-03-29 | 2026-03-29 | 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
id: IHUB-WP-0008-T01
status: done
priority: high
state_hub_task_id: "5c5315b7-98ff-45dc-8eef-a5df83e18ea2"
Add Phase 8 tables to Application/Schema.sql and write migration:
-- 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
id: IHUB-WP-0008-T02
status: done
priority: high
state_hub_task_id: "4d12c8e2-7b8a-4da7-a37d-0663453a3e43"
- Scaffold
WidgetOwnershipController:index: table of all ownership records — widget name, owner hub, steward hub (if different), type badge, effective rangenew/create: assign ownership to a widgetshow: full detailedit/update: change steward hub, ownership type, or effective_until; no delete (audit artifact)
- Validation:
widget_id,owner_hub_id,ownership_typerequiredownership_typemust belocal | delegated | globaleffective_untilmust be aftereffective_fromif setdelegatedtype requiressteward_hub_id ≠ owner_hub_id
- Widget show page: ownership badge (owner hub name + type, colour-coded: local=gray, delegated=blue, global=purple)
- Hub show page: "Owned Widgets" and "Stewarded Widgets" sections (collapsed by default, expandable)
- Link
WidgetOwnershipControllerfrom 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
id: IHUB-WP-0008-T03
status: done
priority: high
state_hub_task_id: "54597bea-bd1f-41ab-bb50-f2f19dc45c01"
- Scaffold
HubRoutingRulesController:index: table of all routing rules — source → target, match criteria, statusnew/createedit/update: notes and match criteriaActivateRoutingRuleAction/DeactivateRoutingRuleAction- No delete
- Add
Application/Helper/RoutingEngine.hswithapplyRoutingRules:- For a given
RequirementCandidate, find active rules for its source hub - Sort by
priority DESC - First matching rule (by
match_categoryand/ormatch_widget_type) setsrouted_to_hub_idon the candidate - Match is
null-inclusive: a nullmatch_categorymatches any category
- For a given
- Call
applyRoutingRulesinRequirementCandidatesController:- After
createRecordinCreateRequirementCandidateAction - In
RouteNowAction { requirementCandidateId }(manual re-evaluation)
- After
- Add
RoutedCandidatesAction { hubId }: candidates withrouted_to_hub_id = hubId, from any source hub — shown in a dedicated view - Candidate show and triage views: show a "Routed to: HubName" badge if
routed_to_hub_idis set; show "Routed from: HubName" if the candidate originated in a different hub - 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
id: IHUB-WP-0008-T04
status: done
priority: high
state_hub_task_id: "df2fcdb1-657f-49d1-b340-79d4f55a9088"
- Scaffold
FederatedPolicyOverlaysController:index: table — title, status badge, scope (all hubs / N hubs), enforced fromshow: full policy text,applies_to_hubslist resolved to hub names, linked decisions countnew/createedit/update: draft only; activated overlays are read-onlyActivateFederatedPolicyAction— setsstatus=active, recordsenforced_from=now(); once active, no further editsRetireFederatedPolicyAction— setsstatus=retired
- Add
PolicyComplianceDashboardAction(global):- For each active overlay: hub scope (all vs specific), count of
DecisionRecords in-scope hubs that reference aPolicyReference - "Coverage %" = decisions with at least one policy reference / total decisions in scope
- For each active overlay: hub scope (all vs specific), count of
- On
DecisionRecordshow page: list any activeFederatedPolicyOverlayrecords whoseapplies_to_hubsincludes this decision's hub (or is[]) - 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
id: IHUB-WP-0008-T05
status: done
priority: medium
state_hub_task_id: "490f37e1-44b2-4667-8213-4498121aaa55"
- Scaffold
StewardshipRolesController:index: all roles across all hubs, grouped by hub — role name, assigned to, granted at, revoked at (if any)shownew/createRevokeRoleAction { stewardshipRoleId }— setsrevoked_at = now()- No delete, no edit (create a new record to replace)
- Hub show page: "Active Stewards" section listing current active
StewardshipRolerecords for this hub (revoked_at IS NULL) DecisionRecordshow page: list stewardship roles active atdecided_atfor the decision's hub —granted_at ≤ decided_at AND (revoked_at IS NULL OR revoked_at > decided_at)- Extend
OperationalReviewBoardViewwith a Panel 5: hubs with zero active stewardship roles - 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
id: IHUB-WP-0008-T06
status: done
priority: medium
state_hub_task_id: "4b59d882-b690-4e14-8460-614bd114ce7a"
- Scaffold
ArchiveRecordsController:index: all archive records — subject type, subject ID (linked if applicable), archived at, reason, archived byshow: full detail includinglineage_ref
- Add
ArchiveWidgetAction { widgetId }: setswidgets.is_archived = true, createsArchiveRecord. Redirect to widget show. Archived widgets show a "Archived" banner; excluded from hub widget counts and triage queries viafilterWhere (#isArchived, False). - 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
ArchiveRecordfor the widget - Read-only timeline view; each step shows count and link to list
- Fetches the full traceability chain in order:
- Link "Lineage" from widget show page
- 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
id: IHUB-WP-0008-T07
status: done
priority: medium
state_hub_task_id: "0c2f6b98-41a5-4876-8bcc-07af08acaf77"
- 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
- Link from global nav as "Federation"
- 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
id: IHUB-WP-0008-T08
status: done
priority: high
state_hub_task_id: "422cae8f-5dc6-4393-b78a-77169b00da8a"
- Integration tests (
Test/):WidgetOwnershipcreate + type transitions (local → delegated → global)HubRoutingRulecreate + activate + candidate routing (applyRoutingRulessetsrouted_to_hub_idfor matching candidate)FederatedPolicyOverlaycreate + activate (edit blocked after activation)StewardshipRolecreate + revoke; contextual query at a past timestampArchiveRecordcreate;is_archived = trueexcludes widget from active queries; lineage fetch returns full chainFederatedGovernanceDashboardcompiles, fetches hubs, returns correct ownership coverage count
- Consistency sync via State Hub MCP:
check_repo_consistency(repo_slug="inter-hub", fix=True) - Documentation updates:
- Update
SCOPE.mdcurrent 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
- Update
- Smoke test checklist:
- Assign
delegatedownership 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
- Assign
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_ownershipsrequires widgets and hubs (T01 before T02)hub_routing_rules+routed_to_hub_idon candidates requires hubs and requirement_candidates (T01 before T03)federated_policy_overlaysis independent but compliance dashboard reads decisions (T04 after T01)stewardship_rolesrequires hubs (T01 before T05)archive_records+is_archivedon 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_untilsignals expiry. The latest active record (effective_from ≤ now, effective_until IS NULL ornow) is the authoritative ownership.
- Routing is additive. Multiple rules can match; only the highest-priority
active rule fires. Re-running
RouteNowActionre-evaluates and may updaterouted_to_hub_id. - Archival is soft-delete.
is_archived = trueon 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).