Files
inter-hub/workplans/IHUB-WP-0008-ihf-phase8-federated-hub-maturity.md
Bernd Worsch 9265ca2d9c feat(P8): IHF Phase 8 complete — Federated Hub Maturity
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>
2026-03-29 22:53:01 +00:00

17 KiB
Raw Blame History

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 17 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"
  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

id: IHUB-WP-0008-T03
status: done
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

id: IHUB-WP-0008-T04
status: done
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 DecisionRecords 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

id: IHUB-WP-0008-T05
status: done
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

id: IHUB-WP-0008-T06
status: done
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

id: IHUB-WP-0008-T07
status: done
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

id: IHUB-WP-0008-T08
status: done
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 17 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 (T02T06 before T07)
  • All feature tasks (T01T07) 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).