Files
inter-hub/Web/Controller/AgentProposals.hs
Bernd Worsch ce42607fca fix(WP-0014/A2): close remaining pure-param and structural compilation errors
Convert all remaining `<- paramOrNothing / param / paramOrDefault /
currentUserOrNothing` monadic binds to `let` — these functions are pure
(ImplicitParams-based) in IHP v1.5, so `<-` is a type error in an IO
do-block.

Controllers fixed:
  AgentDelegations, AiGovernancePolicies, Annotations, ApiConsumers,
  CollectiveProposals, DecisionRecords, DeploymentRecords,
  HubCapabilityManifests, HubRoutingRules, InstitutionalKnowledge,
  OutcomeCorrelations, RequirementCandidates, TypeRegistries,
  WebhookSubscriptions, Widgets,
  Api/V2/{Annotations,InteractionEvents,Token}

WebhookSubscriptions: remove orphaned `Right () ->` case arm that was
left inside a bare `unless` block (structural parse error).

Also carries forward all in-progress fixes from the working tree:
  helpers (AgentBridge, ApiRateLimit, BottleneckDetector,
            CrossHubPropagation, FrictionScore),
  views (CanSelect instances, HSX lambda extraction, formFor wrappers),
  env/build (envrc GHCi perms, flake.nix Tailwind + GHC resource limits,
             static/app.css additional Tailwind output).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 01:14:08 +00:00

129 lines
5.7 KiB
Haskell

module Web.Controller.AgentProposals where
import Web.Types
import Web.View.AgentProposals.Index
import Web.View.AgentProposals.Show
import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
import Data.Aeson (decode)
import Data.ByteString.Lazy (fromStrict)
import Data.Text.Encoding (encodeUtf8)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
instance Controller AgentProposalsController where
beforeAction = ensureIsUser
action AgentProposalsAction = do
let mTypeFilter = paramOrNothing @Text "proposal_type"
mStatusFilter = paramOrNothing @Text "status"
proposals <- case (mTypeFilter, mStatusFilter) of
(Nothing, Nothing) ->
query @AgentProposal |> orderByDesc #createdAt |> fetch
(Just t, Nothing) ->
query @AgentProposal
|> filterWhere (#proposalType, t)
|> orderByDesc #createdAt |> fetch
(Nothing, Just s) ->
query @AgentProposal
|> filterWhere (#status, s)
|> orderByDesc #createdAt |> fetch
(Just t, Just s) ->
query @AgentProposal
|> filterWhere (#proposalType, t)
|> filterWhere (#status, s)
|> orderByDesc #createdAt |> fetch
widgets <- query @Widget |> fetch
render IndexView { proposals, widgets, mTypeFilter, mStatusFilter }
action ShowAgentProposalAction { agentProposalId } = do
proposal <- fetch agentProposalId
mWidget <- case proposal.sourceWidgetId of
Nothing -> pure Nothing
Just wid -> fetchOneOrNothing wid
mCandidate <- case proposal.sourceCandidateId of
Nothing -> pure Nothing
Just cid -> fetchOneOrNothing cid
mDecision <- case proposal.sourceDecisionId of
Nothing -> pure Nothing
Just did -> fetchOneOrNothing did
mReview <- query @AgentReviewRecord
|> filterWhere (#proposalId, agentProposalId)
|> fetchOneOrNothing
confidences <- query @ConfidenceAnnotation
|> filterWhere (#proposalId, agentProposalId)
|> orderByAsc #createdAt
|> fetch
users <- query @User |> fetch
render ShowView
{ proposal, mWidget, mCandidate, mDecision
, mReview, confidences, users }
action AcceptProposalAction { agentProposalId } = do
proposal <- fetch agentProposalId
mExisting <- query @AgentReviewRecord
|> filterWhere (#proposalId, agentProposalId)
|> fetchOneOrNothing
case mExisting of
Just _ -> do
setSuccessMessage "Already reviewed"
redirectTo ShowAgentProposalAction { agentProposalId }
Nothing -> do
let mUser = currentUserOrNothing
let reviewerId = fmap (.id) mUser
proposal
|> set #status "accepted"
|> updateRecord
let notes = paramOrNothing @Text "notes"
newRecord @AgentReviewRecord
|> set #proposalId agentProposalId
|> set #reviewerId (reviewerId)
|> set #decision "accepted"
|> set #notes notes
|> createRecord
-- T04: if requirement_draft, promote to RequirementCandidate
when (proposal.proposalType == "requirement_draft") do
let mParsed = decode (fromStrict (encodeUtf8 proposal.content))
:: Maybe (HashMap Text Text)
case (mParsed, proposal.sourceWidgetId) of
(Just m, Just srcWid) -> do
let title = fromMaybe "AI Draft" (HashMap.lookup "title" m)
desc = fromMaybe "" (HashMap.lookup "description" m)
newRecord @RequirementCandidate
|> set #title title
|> set #description desc
|> set #sourceWidgetId srcWid
|> set #category "friction"
|> set #status "open"
|> createRecord
setSuccessMessage "Requirement candidate created from AI draft"
_ ->
setSuccessMessage "Proposal accepted (could not create candidate)"
redirectTo ShowAgentProposalAction { agentProposalId }
action RejectProposalAction { agentProposalId } = do
proposal <- fetch agentProposalId
mExisting <- query @AgentReviewRecord
|> filterWhere (#proposalId, agentProposalId)
|> fetchOneOrNothing
case mExisting of
Just _ -> do
setSuccessMessage "Already reviewed"
redirectTo ShowAgentProposalAction { agentProposalId }
Nothing -> do
let mUser = currentUserOrNothing
let reviewerId = fmap (.id) mUser
proposal
|> set #status "rejected"
|> updateRecord
let notes = paramOrNothing @Text "notes"
newRecord @AgentReviewRecord
|> set #proposalId agentProposalId
|> set #reviewerId (reviewerId)
|> set #decision "rejected"
|> set #notes notes
|> createRecord
setSuccessMessage "Proposal rejected"
redirectTo ShowAgentProposalAction { agentProposalId }