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)
Backend — RemediationWorkflowService.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
Frontend — StageVerificationPanel (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:
- A
RemediationWorkflow history view on the software remediation page — shows all past workflows (via RecurrenceSourceWorkflowId chain), their decisions, justifications, and who approved them
- Make this lineage visible during the Verification stage so the verifier has full context
- 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
- 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?
- Who counts as a "responsible party" for verification? The original
DecidedBy user? The assigned team? The role assigned to the approval stage? Some combination?
- 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?
- 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?
- 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)
Acceptance criteria (Option B, for reference)
Affected code
Backend
src/PatchHound.Core/Entities/RemediationWorkflow.cs — RecurrenceSourceWorkflowId, possible VerificationMode
src/PatchHound.Core/Entities/RemediationWorkflowStageRecord.cs — confirmation tracking (Option A)
src/PatchHound.Infrastructure/Services/RemediationWorkflowService.cs — VerifyRecurringWorkflowAsync, new CheckAndFinalizeVerificationAsync (Option A)
src/PatchHound.Infrastructure/Services/RemediationDecisionService.cs — VerifyAndCarryForwardDecisionAsync
- New entity + EF config:
RemediationVerificationConfirmation (Option A only)
Frontend
frontend/src/components/features/remediation/SoftwareRemediationView.tsx — StageVerificationPanel (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
Summary
When a vulnerability re-occurs for a software scope that already had a completed remediation workflow, the current system opens a new
RemediationWorkflowstarting at theVerificationstage and links it viaRecurrenceSourceWorkflowId. 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
RiskAcceptancethey never owned, or a software owner rubber-stamping anAlternateMitigationthey didn't implement, provides no real assurance. This issue tracks improving recurrence handling so that verification is meaningful.Current behaviour (for context)
Backend —
RemediationWorkflowService.GetOrCreateActiveWorkflowAsync(src/PatchHound.Infrastructure/Services/RemediationWorkflowService.cs:36):RecurrenceSourceWorkflowIdRemediationWorkflowStage.Verificationwith a single stage recordVerifyRecurringWorkflowAsyncacceptskeepCurrentDecisionorchooseNewDecisionfrom any single userFrontend —
StageVerificationPanel(frontend/src/components/features/remediation/SoftwareRemediationView.tsx:1134):Data model:
RemediationWorkflow.RecurrenceSourceWorkflowId— lineage pointer (exists,src/PatchHound.Core/Entities/RemediationWorkflow.cs:13)RemediationWorkflowStageRecord— one record per stage, no per-party confirmation sub-recordsRemediationDecision.DecidedBy/ApprovedBy— individual actor references on the original decisionProposed approach
Option A — Multi-party confirmation within the existing Verification stage
Extend the
Verificationstage to require each responsible party to explicitly confirm that their aspect of the previous decision is still valid, before the stage can auto-advance toClosure(carry-forward path) or move toRemediationDecision(new-decision path).Responsible parties are determined by the original decision's outcome:
RiskAcceptanceAlternateMitigationApprovedForPatchingPatchingDeferredModel changes:
RemediationVerificationConfirmation—(WorkflowId, PartyType, PartyId, ConfirmedAt, Action: keep/replace, Notes?)RemediationWorkflowStageRecordgains a computedRequiredConfirmationCount/ReceivedConfirmationCount(or the stage auto-advances once all confirmations arrive)RemediationWorkflowgainsVerificationMode:SingleActor(current) /MultiParty(new default for non-trivial outcomes)Service changes (
RemediationWorkflowService):VerifyRecurringWorkflowAsynctransitions to recording aRemediationVerificationConfirmationrather than immediately closing the stageCheckAndFinalizeVerificationAsync— called after each confirmation; if all required parties have confirmed the same direction, auto-advances the stageFrontend changes:
StageVerificationPanelreplaced with a richer "Awaiting confirmations" panel showing each required party, their confirmation status, and a "Confirm / Request new decision" actionOption B — New workflow with historical lineage view (simpler)
Keep the current single-actor verification as-is. Instead of requiring multi-party confirmation, add:
RemediationWorkflowhistory view on the software remediation page — shows all past workflows (viaRecurrenceSourceWorkflowIdchain), their decisions, justifications, and who approved themThis option requires no new entity, no confirmation state machine. It improves context but does not enforce accountability.
Design questions to resolve before implementation
DecidedByuser? The assigned team? The role assigned to the approval stage? Some combination?RemediationDecisionon any disagreement, or only when a majority chooses replace?Acceptance criteria (Option A, for reference)
RiskAcceptanceandAlternateMitigationrequire confirmation from both the Security Manager role and the Software Owner Team before the Verification stage closesApprovedForPatchingandPatchingDeferredrequire confirmation from the Software Owner Team onlykeep/replace)StageVerificationPanelshows pending / confirmed parties and enables action only for parties yet to confirmAcceptance criteria (Option B, for reference)
RecurrenceSourceWorkflowIdAffected code
Backend
src/PatchHound.Core/Entities/RemediationWorkflow.cs—RecurrenceSourceWorkflowId, possibleVerificationModesrc/PatchHound.Core/Entities/RemediationWorkflowStageRecord.cs— confirmation tracking (Option A)src/PatchHound.Infrastructure/Services/RemediationWorkflowService.cs—VerifyRecurringWorkflowAsync, newCheckAndFinalizeVerificationAsync(Option A)src/PatchHound.Infrastructure/Services/RemediationDecisionService.cs—VerifyAndCarryForwardDecisionAsyncRemediationVerificationConfirmation(Option A only)Frontend
frontend/src/components/features/remediation/SoftwareRemediationView.tsx—StageVerificationPanel(line 1134)frontend/src/components/features/remediation/RemediationStageRail.tsx— no changes expectedVerificationConfirmationPanelorWorkflowHistoryDrawer(Option B)API
src/PatchHound.Api/Controllers/RemediationDecisionsController.cs— new endpoint for per-party confirmation (Option A)Related
SoftwareProduct; this issue should target post-Phase 4)