fix(WP-0014/A2): continued type-correctness fixes and Tailwind CSS output

- Schema.sql: add FK constraints for phases 6–12 so IHP generates Id X
  instead of UUID for FK columns (widget_adapter_specs, friction_scores,
  hub_routing_rules, agent_proposals, hub_capability_manifests, etc.)
- HubHealth, ModelRouter, ApiInteractionEvents: remove toUUID() wrappers
  now that FK columns carry proper Id types
- FederatedGovernance/Dashboard, HubRoutingRules/Index: same Id comparison fix
- AgentProposals/Index, DecisionRecords/Index, ApiConsumers/Edit: Id type fixes
- BottleneckDetector: add Data.Coerce import; CrossHubPropagation: add guard
- ApiKeys: qualify cryptohash-sha256 import to resolve package ambiguity
- WebhookDeliveryJob: use LBS.fromStrict; remove duplicate diffUTCTime
- Sessions/New: use renderFlashMessages (IHP built-in)
- ArchiveRecords/LineageInspector: simplify renderChainStep signature
- static/app.css: Tailwind CSS output (2011 lines) — A3 confirmed
- workplans/IHUB-WP-0015-local-deployment-intro-ui.md: add workplan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 01:49:41 +00:00
parent 34cc18b4c7
commit 74bab5f6f2
17 changed files with 2218 additions and 32 deletions

View File

@@ -80,7 +80,7 @@ createEventForHub hub = do
Nothing -> do
renderJsonWithStatusCode status422 (object ["error" .= ("Widget not found" :: Text)])
Just widget -> do
when (widget.hubId /= toUUID hub.id) do
when (widget.hubId /= hub.id) do
renderJsonWithStatusCode status403 (object ["error" .= ("Widget does not belong to this hub" :: Text)])
event <- newRecord @InteractionEvent

View File

@@ -7,7 +7,7 @@ import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
import qualified Data.Text.Encoding as TE
import qualified Crypto.Hash.SHA256 as SHA256
import qualified "cryptohash-sha256" Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Random as Random

View File

@@ -52,7 +52,7 @@ attempt sub payload attemptNo = do
$ HTTP.setRequestHeader "Content-Type" ["application/json"]
$ HTTP.setRequestHeader "X-IHF-Signature" [TE.encodeUtf8 sig]
$ HTTP.setRequestHeader "X-IHF-Event" [TE.encodeUtf8 sub.eventType]
$ HTTP.setRequestBodyBS payloadBytes req
$ HTTP.setRequestBodyLBS (LBS.fromStrict payloadBytes) req
HTTP.httpLBS req'
endTime <- getCurrentTime
let latencyMs = round (realToFrac (diffUTCTime endTime startTime) * 1000 :: Double) :: Int
@@ -104,5 +104,3 @@ hmacSha256Hex secret payload =
digest = SHA256.hash combined
in TE.decodeUtf8 (Base16.encode digest)
diffUTCTime :: UTCTime -> UTCTime -> NominalDiffTime
diffUTCTime a b = Data.Time.diffUTCTime a b

View File

@@ -97,7 +97,7 @@ renderRow widgets p = [hsx|
</a>
</td>
<td class="px-4 py-3 text-gray-600">{widgetName widgets p.sourceWidgetId}</td>
<td class="px-4 py-3">{renderConfidenceBar p.confidence}</td>
<td class="px-4 py-3">{renderConfidenceBar (fmap realToFrac p.confidence)}</td>
<td class="px-4 py-3">
<span class={statusBadge p.status <> " text-xs px-2 py-0.5 rounded font-medium"}>
{p.status}
@@ -107,9 +107,9 @@ renderRow widgets p = [hsx|
</tr>
|]
widgetName :: [Widget] -> Maybe UUID -> Text
widgetName :: [Widget] -> Maybe (Id Widget) -> Text
widgetName _ Nothing = ""
widgetName widgets (Just wid) = maybe "(unknown)" (.name) (find (\w -> toUUID w.id == wid) widgets)
widgetName widgets (Just wid) = maybe "(unknown)" (.name) (find (\w -> w.id == wid) widgets)
renderConfidenceBar :: Maybe Double -> Html
renderConfidenceBar Nothing = [hsx|<span class="text-gray-300 text-xs"></span>|]

View File

@@ -54,7 +54,7 @@ instance View EditView where
|]
where
manifestOption selectedId m = [hsx|
<option value={show m.id} selected={selectedId == Just (toUUID m.id)}>
<option value={show m.id} selected={selectedId == Just m.id}>
Manifest {show m.id} ({m.status})
</option>
|]

View File

@@ -31,14 +31,14 @@ instance View LineageInspectorView where
<p class="text-sm text-gray-500 mb-6">Full traceability chain for this widget.</p>
<div class="space-y-4">
{renderChainStep "1" "Widget" 1 (Just $ ShowWidgetAction { widgetId = widget.id })}
{renderChainStep "2" "Interaction Events" (length events) Nothing}
{renderChainStep "3" "Annotations" (length annotations) Nothing}
{renderChainStep "4" "Requirement Candidates" (length candidates) Nothing}
{renderChainStep "5" "Requirements" (length requirements) Nothing}
{renderChainStep "6" "Decision Records" (length decisions) Nothing}
{renderChainStep "7" "Deployments" (length deployments) Nothing}
{renderChainStep "8" "Outcome Signals" (length signals) Nothing}
{renderChainStep "1" "Widget" 1}
{renderChainStep "2" "Interaction Events" (length events)}
{renderChainStep "3" "Annotations" (length annotations)}
{renderChainStep "4" "Requirement Candidates" (length candidates)}
{renderChainStep "5" "Requirements" (length requirements)}
{renderChainStep "6" "Decision Records" (length decisions)}
{renderChainStep "7" "Deployments" (length deployments)}
{renderChainStep "8" "Outcome Signals" (length signals)}
</div>
{maybe mempty renderArchivePanel mArchive}
@@ -68,8 +68,8 @@ instance View LineageInspectorView where
</div>
|]
renderChainStep :: Text -> Text -> Int -> Maybe a -> Html
renderChainStep stepNum label count mLink = [hsx|
renderChainStep :: Text -> Text -> Int -> Html
renderChainStep stepNum label count = [hsx|
<div class="flex items-center gap-4">
<div class="w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 flex items-center justify-center text-sm font-medium flex-shrink-0">
{stepNum}

View File

@@ -94,9 +94,9 @@ linkedReqTitle :: [Requirement] -> Maybe (Id Requirement) -> Text
linkedReqTitle _ Nothing = ""
linkedReqTitle reqs (Just rid) = maybe "(unknown)" (.title) (find (\r -> r.id == rid) reqs)
userName :: [User] -> Maybe UUID -> Text
userName :: [User] -> Maybe (Id User) -> Text
userName _ Nothing = ""
userName users (Just uid) = maybe "(unknown)" (.name) (find (\u -> toUUID u.id == uid) users)
userName users (Just uid) = maybe "(unknown)" (.name) (find (\u -> u.id == uid) users)
outcomeClass :: Text -> Text
outcomeClass "accepted" = "bg-green-100 text-green-800"

View File

@@ -81,7 +81,7 @@ instance View FederatedGovernanceDashboardView where
-- ── Panel 2: Routing activity ─────────────────────────────────────
activeRulesCount = length rules
routedCount = length routedCandidates
hubName hid = maybe (show hid) (.name) (find (\h -> toUUID h.id == hid) hubs)
hubName hid = maybe (show hid) (.name) (find (\h -> h.id == hid) hubs)
panel2Routing = [hsx|
<div class="bg-white rounded-lg border border-gray-200 p-5">
@@ -159,7 +159,7 @@ instance View FederatedGovernanceDashboardView where
hubsWithStewards = List.nub (map (.hubId) stewards)
stewardedCount = length hubsWithStewards
totalHubs = length hubs
hubsWithNoSteward = filter (\h -> toUUID h.id `notElem` hubsWithStewards) hubs
hubsWithNoSteward = filter (\h -> h.id `notElem` hubsWithStewards) hubs
panel4Stewardship = [hsx|
<div class="bg-white rounded-lg border border-gray-200 p-5">

View File

@@ -47,7 +47,7 @@ renderRulesList rules hubs = [hsx|
renderRoutingRuleRow :: [Hub] -> HubRoutingRule -> Html
renderRoutingRuleRow hubs r =
let hubName hid = maybe (show hid) (.name) (find (\h -> toUUID h.id == hid) hubs)
let hubName hid = maybe (show hid) (.name) (find (\h -> h.id == hid) hubs)
in [hsx|
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-800">

View File

@@ -13,7 +13,7 @@ instance View NewView where
<div class="max-w-sm mx-auto mt-16">
<h1 class="text-2xl font-semibold mb-6">Sign in to inter-hub</h1>
<form method="POST" action={CreateSessionAction} class="space-y-4">
{forEach (getFlashMessages) renderFlash}
{renderFlashMessages}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" name="email" required
@@ -35,7 +35,3 @@ instance View NewView where
</div>
|]
renderFlash :: Text -> Html
renderFlash msg = [hsx|
<div class="bg-red-50 border border-red-200 text-red-700 rounded px-3 py-2 text-sm">{msg}</div>
|]