Skip to content

feat: Multi-party verification workflow for recurring remediations #21

@FrodeHus

Description

@FrodeHus

Summary

When a vulnerability re-occurs for a software scope that already had a completed remediation workflow, the current system opens a new RemediationWorkflow starting at the Verification stage and links it via RecurrenceSourceWorkflowId. A single actor can either carry the previous decision forward or send it back for a new decision.

The gap: only one party acts on verification, and they do so without structured confirmation from the responsible parties who originally made or approved the previous decision. A security analyst re-verifying a RiskAcceptance they never owned, or a software owner rubber-stamping an AlternateMitigation they didn't implement, provides no real assurance. This issue tracks improving recurrence handling so that verification is meaningful.


Current behaviour (for context)

BackendRemediationWorkflowService.GetOrCreateActiveWorkflowAsync (src/PatchHound.Infrastructure/Services/RemediationWorkflowService.cs:36):

  • Detects a completed previous workflow via RecurrenceSourceWorkflowId
  • Opens new workflow at RemediationWorkflowStage.Verification with a single stage record
  • VerifyRecurringWorkflowAsync accepts keepCurrentDecision or chooseNewDecision from any single user

FrontendStageVerificationPanel (frontend/src/components/features/remediation/SoftwareRemediationView.tsx:1134):

  • Shows the previous decision outcome + justification
  • Two buttons: "Keep current decision" / "Send back for a new decision"
  • No information about who needs to confirm, no pending-confirmations state

Data model:

  • RemediationWorkflow.RecurrenceSourceWorkflowId — lineage pointer (exists, src/PatchHound.Core/Entities/RemediationWorkflow.cs:13)
  • RemediationWorkflowStageRecord — one record per stage, no per-party confirmation sub-records
  • RemediationDecision.DecidedBy / ApprovedBy — individual actor references on the original decision

Proposed approach

Option A — Multi-party confirmation within the existing Verification stage

Extend the Verification stage to require each responsible party to explicitly confirm that their aspect of the previous decision is still valid, before the stage can auto-advance to Closure (carry-forward path) or move to RemediationDecision (new-decision path).

Responsible parties are determined by the original decision's outcome:

Previous outcome Parties required to confirm
RiskAcceptance Security Manager (original approver role) + Software Owner Team
AlternateMitigation Security Manager + Software Owner Team
ApprovedForPatching Software Owner Team
PatchingDeferred Software Owner Team

Model changes:

  • New entity: RemediationVerificationConfirmation(WorkflowId, PartyType, PartyId, ConfirmedAt, Action: keep/replace, Notes?)
  • RemediationWorkflowStageRecord gains a computed RequiredConfirmationCount / ReceivedConfirmationCount (or the stage auto-advances once all confirmations arrive)
  • RemediationWorkflow gains VerificationMode: SingleActor (current) / MultiParty (new default for non-trivial outcomes)

Service changes (RemediationWorkflowService):

  • VerifyRecurringWorkflowAsync transitions to recording a RemediationVerificationConfirmation rather than immediately closing the stage
  • New method: CheckAndFinalizeVerificationAsync — called after each confirmation; if all required parties have confirmed the same direction, auto-advances the stage
  • If parties disagree (one says keep, one says replace), the stage remains open and a conflict summary is surfaced
  • Notifications sent to parties when a new verification is required (plug into existing notification infrastructure)

Frontend changes:

  • StageVerificationPanel replaced with a richer "Awaiting confirmations" panel showing each required party, their confirmation status, and a "Confirm / Request new decision" action
  • If the current user is a required party and hasn't confirmed, their action buttons are enabled
  • History tab gains a "Previous workflow" link and a cross-workflow decision history view

Option B — New workflow with historical lineage view (simpler)

Keep the current single-actor verification as-is. Instead of requiring multi-party confirmation, add:

  1. A RemediationWorkflow history view on the software remediation page — shows all past workflows (via RecurrenceSourceWorkflowId chain), their decisions, justifications, and who approved them
  2. Make this lineage visible during the Verification stage so the verifier has full context
  3. Optionally surface the lineage in a read-only "Previous decisions" drawer

This option requires no new entity, no confirmation state machine. It improves context but does not enforce accountability.


Design questions to resolve before implementation

  1. Which option? Option A (enforceable multi-party, more complex) or Option B (context-only history, simpler)? Or a phased approach: ship Option B now, follow with Option A?
  2. Who counts as a "responsible party" for verification? The original DecidedBy user? The assigned team? The role assigned to the approval stage? Some combination?
  3. What happens if a required party is no longer in the system (e.g. user deactivated, team disbanded)? Should verification fall back to single-actor? Should it require a replacement assignment?
  4. Conflict resolution: If Option A is chosen and parties disagree on keep/replace, who breaks the tie? Does the workflow automatically go to RemediationDecision on any disagreement, or only when a majority chooses replace?
  5. Timeout: Should a verification stage time out (similar to approval expiry)? If no confirmation arrives within N hours, should it auto-advance or escalate?

Acceptance criteria (Option A, for reference)

  • Recurring workflows for RiskAcceptance and AlternateMitigation require confirmation from both the Security Manager role and the Software Owner Team before the Verification stage closes
  • Recurring workflows for ApprovedForPatching and PatchingDeferred require confirmation from the Software Owner Team only
  • Each party's confirmation is recorded with timestamp, actor, and chosen direction (keep / replace)
  • Verification stage auto-advances once all required confirmations are received and agree
  • If confirmations disagree, the stage surfaces a conflict and defaults to sending back for a new decision
  • Notifications are sent to required parties when a new verification is required
  • StageVerificationPanel shows pending / confirmed parties and enables action only for parties yet to confirm
  • Cross-workflow decision history is visible on the software remediation page (applies to both options)
  • Verification timeout is configurable per tenant (or uses the existing approval expiry setting)

Acceptance criteria (Option B, for reference)

  • The software remediation page shows a full list of previous workflows for the same software scope, linked via RecurrenceSourceWorkflowId
  • Each previous workflow entry shows: decision outcome, justification, who decided, who approved, dates, and whether it was closed by recurrence or by resolution
  • The Verification stage panel displays the full lineage (not just the immediate previous decision)
  • No new entities or state machine changes required

Affected code

Backend

  • src/PatchHound.Core/Entities/RemediationWorkflow.csRecurrenceSourceWorkflowId, possible VerificationMode
  • src/PatchHound.Core/Entities/RemediationWorkflowStageRecord.cs — confirmation tracking (Option A)
  • src/PatchHound.Infrastructure/Services/RemediationWorkflowService.csVerifyRecurringWorkflowAsync, new CheckAndFinalizeVerificationAsync (Option A)
  • src/PatchHound.Infrastructure/Services/RemediationDecisionService.csVerifyAndCarryForwardDecisionAsync
  • New entity + EF config: RemediationVerificationConfirmation (Option A only)

Frontend

  • frontend/src/components/features/remediation/SoftwareRemediationView.tsxStageVerificationPanel (line 1134)
  • frontend/src/components/features/remediation/RemediationStageRail.tsx — no changes expected
  • New component: VerificationConfirmationPanel or WorkflowHistoryDrawer (Option B)

API

  • src/PatchHound.Api/Controllers/RemediationDecisionsController.cs — new endpoint for per-party confirmation (Option A)
  • New DTOs for confirmation state and workflow lineage

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions