Files
inter-hub/Web/Controller/HubRoutingRules.hs
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

90 lines
3.6 KiB
Haskell

module Web.Controller.HubRoutingRules where
import Web.Types
import Web.View.HubRoutingRules.Index
import Web.View.HubRoutingRules.Show
import Web.View.HubRoutingRules.New
import Web.View.HubRoutingRules.Edit
import Web.View.HubRoutingRules.RoutedCandidates
import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
import Application.Helper.RoutingEngine (applyRoutingRules)
instance Controller HubRoutingRulesController where
beforeAction = ensureIsUser
action HubRoutingRulesAction = autoRefresh do
rules <- query @HubRoutingRule |> orderByDesc #priority |> fetch
hubs <- query @Hub |> fetch
render IndexView { rules, hubs }
action ShowHubRoutingRuleAction { hubRoutingRuleId } = do
rule <- fetch hubRoutingRuleId
sourceHub <- fetch rule.sourceHubId
targetHub <- fetch rule.targetHubId
render ShowView { rule, sourceHub, targetHub }
action NewHubRoutingRuleAction = do
let rule = newRecord @HubRoutingRule
hubs <- query @Hub |> orderByAsc #name |> fetch
render NewView { rule, hubs }
action CreateHubRoutingRuleAction = do
let rule = newRecord @HubRoutingRule
hubs <- query @Hub |> orderByAsc #name |> fetch
rule
|> fill @'["sourceHubId","targetHubId","matchCategory","matchWidgetType","priority","notes"]
|> validateField #sourceHubId nonEmpty
|> validateField #targetHubId nonEmpty
|> ifValid \case
Left r -> render NewView { rule = r, hubs }
Right r -> do
r <- createRecord r
setSuccessMessage "Routing rule created"
redirectTo ShowHubRoutingRuleAction { hubRoutingRuleId = r.id }
action EditHubRoutingRuleAction { hubRoutingRuleId } = do
rule <- fetch hubRoutingRuleId
hubs <- query @Hub |> orderByAsc #name |> fetch
render EditView { rule, hubs }
action UpdateHubRoutingRuleAction { hubRoutingRuleId } = do
rule <- fetch hubRoutingRuleId
hubs <- query @Hub |> orderByAsc #name |> fetch
rule
|> fill @'["matchCategory","matchWidgetType","priority","notes"]
|> ifValid \case
Left r -> render EditView { rule = r, hubs }
Right r -> do
updateRecord r
setSuccessMessage "Routing rule updated"
redirectTo ShowHubRoutingRuleAction { hubRoutingRuleId = r.id }
action ActivateRoutingRuleAction { hubRoutingRuleId } = do
rule <- fetch hubRoutingRuleId
rule |> set #status "active" |> updateRecord
setSuccessMessage "Rule activated"
redirectTo HubRoutingRulesAction
action DeactivateRoutingRuleAction { hubRoutingRuleId } = do
rule <- fetch hubRoutingRuleId
rule |> set #status "inactive" |> updateRecord
setSuccessMessage "Rule deactivated"
redirectTo HubRoutingRulesAction
action RoutedCandidatesAction { hubId } = autoRefresh do
hub <- fetch hubId
candidates <- query @RequirementCandidate
|> filterWhere (#routedToHubId, Just hubId)
|> orderByDesc #createdAt
|> fetch
render RoutedCandidatesView { hub, candidates }
action RouteNowAction { requirementCandidateId } = do
candidate <- fetch requirementCandidateId
widgets <- query @Widget |> fetch
_ <- applyRoutingRules candidate widgets
setSuccessMessage "Routing re-evaluated"
redirectTo ShowRequirementCandidateAction { requirementCandidateId }