Payload/scripts/kaden/advisory/AdvisoryDecisionSlate.ps1
|
# Dot-sourced into AdvisoryWorkflow.psm1 - numbered decision slate binding. function Get-AdvisorySlateTrimmedGroup { param($Group) if ($null -eq $Group) { return '' } return ([string]$Group).Trim() } function New-AdvisorySlateLineToken { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Returns an in-memory slate token. It does not change system state.')] param( [Parameter(Mandatory = $true)][int]$Position, [Parameter(Mandatory = $true)][string]$Decision, [string]$Direction = '', [string]$Owner = '', [switch]$OwnerNameOmitted ) return [pscustomobject]@{ ok = $true position = $Position decision = $Decision direction = $Direction owner = $Owner owner_name_omitted = [bool]$OwnerNameOmitted } } function ConvertFrom-AdvisorySlateLineToken { <# .SYNOPSIS Parses one numbered slate line into a decision, an optional named owner, and an optional rationale. .DESCRIPTION `3 Fix owner spike-runner` is a Fix whose next owner is the named token. The finding's route stays the destination. A numbered Fix that names no owner leaves owner empty. The binder then uses the route default, or returns the finding id in needs_owner when the route has none. `3 Fix owner` with the name omitted is not a default-owner Fix. That finding id is returned in needs_owner. #> param([Parameter(Mandatory = $true)][string]$Line) $labels = @( @{ Pattern = '^\s*(\d+)\s+Accept\s+Risk(?:\s*(?:\u2014|-)\s*(.*))?$'; Decision = 'Accept Risk' } @{ Pattern = '^\s*(\d+)\s+Promote\s+To\s+Backlog(?:\s*(?:\u2014|-)\s*(.*))?$'; Decision = 'Promote To Backlog' } @{ Pattern = '^\s*(\d+)\s+More\s+Info(?:\s*(?:\u2014|-)\s*(.*))?$'; Decision = 'More Info' } ) foreach ($label in $labels) { if ($Line -match [string]$label.Pattern) { return New-AdvisorySlateLineToken -Position ([int]$Matches[1]) -Decision ([string]$label.Decision) -Direction (Get-AdvisorySlateTrimmedGroup $Matches[2]) } } if ($Line -match '^\s*(\d+)\s+Fix\s+owner\s*$') { return New-AdvisorySlateLineToken -Position ([int]$Matches[1]) -Decision 'Fix' -OwnerNameOmitted } if ($Line -match '^\s*(\d+)\s+Fix\s+owner\s+(\S+)(?:\s*(?:\u2014|-)\s*(.*))?$') { return New-AdvisorySlateLineToken -Position ([int]$Matches[1]) -Decision 'Fix' -Owner ([string]$Matches[2]) -Direction (Get-AdvisorySlateTrimmedGroup $Matches[3]) } if ($Line -match '^\s*(\d+)\s+(\S+)(?:\s*(?:\u2014|-)\s*(.*))?$') { return New-AdvisorySlateLineToken -Position ([int]$Matches[1]) -Decision ([string]$Matches[2]) -Direction (Get-AdvisorySlateTrimmedGroup $Matches[3]) } return [pscustomobject]@{ ok = $false; position = 0; decision = ''; direction = ''; owner = ''; owner_name_omitted = $false } } function New-AdvisorySlateBoundRow { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Returns an in-memory bound slate row. It does not change system state.')] param( [Parameter(Mandatory = $true)][int]$Position, [Parameter(Mandatory = $true)][string]$FindingId, [Parameter(Mandatory = $true)][string]$Decision, [string]$Rationale = '', [string]$Destination = '', $Owner = $null, [string]$Trigger = '', [string]$Status = 'Open' ) return [pscustomobject]@{ position = $Position finding_id = $FindingId decision = $Decision rationale = $Rationale selected_work_destination = $Destination next_owner = $Owner trigger_or_needed_information = $Trigger status = $Status } } function New-AdvisorySlateBindResult { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Returns an in-memory slate bind result. It does not change system state.')] param( $Bound, $NeedsDirection, $NeedsOwner ) return [pscustomobject]@{ direction_satisfied = (@($NeedsDirection).Count -eq 0) rows = @($Bound) needs_direction = @($NeedsDirection) needs_owner = @($NeedsOwner) } } function Get-AdvisorySlateDecisionPolicy { param([Parameter(Mandatory = $true)][string]$Decision) switch ($Decision) { 'Defer' { return @{ RequiresDirection = $true; CopyDirectionToTrigger = $true; Destination = 'deferred-work'; AcceptRisk = $false } } 'Promote To Backlog' { return @{ RequiresDirection = $true; CopyDirectionToTrigger = $true; Destination = $null; AcceptRisk = $false } } 'More Info' { return @{ RequiresDirection = $true; CopyDirectionToTrigger = $true; Destination = $null; AcceptRisk = $false } } 'Accept Risk' { return @{ RequiresDirection = $false; CopyDirectionToTrigger = $false; Destination = 'none'; AcceptRisk = $true } } default { return @{ RequiresDirection = $false; CopyDirectionToTrigger = $false; Destination = $null; AcceptRisk = $false } } } } function Add-AdvisorySlateAllFixRow { param( $Findings, $Bound, $NeedsOwner ) for ($i = 0; $i -lt @($Findings).Count; $i++) { $finding = @($Findings)[$i] $route = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'route_to') $owner = Get-AdvisoryDefaultOwnerForRoute -RouteTo $route $findingId = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'finding_id') if ([string]::IsNullOrWhiteSpace([string]$owner)) { [void]$NeedsOwner.Add($findingId) continue } $recommendation = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'recommendation') [void]$Bound.Add((New-AdvisorySlateBoundRow -Position ($i + 1) -FindingId $findingId -Decision 'Fix' -Rationale $recommendation -Destination $route -Owner $owner)) } } function Add-AdvisorySlateNumberedRow { param( [string[]]$Lines, $Findings, $Bound, $NeedsDirection, $NeedsOwner ) foreach ($line in @($Lines)) { $token = ConvertFrom-AdvisorySlateLineToken -Line $line if (-not $token.ok) { continue } $position = [int]$token.position $decision = [string]$token.decision $direction = [string]$token.direction if ($position -lt 1 -or $position -gt @($Findings).Count) { continue } $finding = @($Findings)[$position - 1] $findingId = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'finding_id') $route = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'route_to') $recommendation = [string](Get-AdvisoryWorkflowProperty -Record $finding -Name 'recommendation') $policy = Get-AdvisorySlateDecisionPolicy -Decision $decision if ($policy.RequiresDirection -and [string]::IsNullOrWhiteSpace($direction)) { [void]$NeedsDirection.Add([pscustomobject]@{ position = $position; finding_id = $findingId; decision = $decision }) continue } $owner = Get-AdvisoryDefaultOwnerForRoute -RouteTo $route $namedOwner = [string]$token.owner if ($decision -eq 'Fix' -and $token.owner_name_omitted) { [void]$NeedsOwner.Add($findingId) continue } if ($decision -eq 'Fix' -and -not [string]::IsNullOrWhiteSpace($namedOwner)) { $owner = $namedOwner } elseif ($decision -eq 'Fix' -and [string]::IsNullOrWhiteSpace([string]$owner)) { [void]$NeedsOwner.Add($findingId) continue } $destination = $route $status = 'Open' $rationale = if ([string]::IsNullOrWhiteSpace($direction)) { $recommendation } else { $direction } $trigger = '' if ($policy.CopyDirectionToTrigger) { $trigger = $direction } if ($null -ne $policy.Destination) { $destination = [string]$policy.Destination } if ($policy.AcceptRisk) { $owner = $null $status = 'Accepted' } [void]$Bound.Add((New-AdvisorySlateBoundRow -Position $position -FindingId $findingId -Decision $decision -Rationale $rationale -Destination $destination -Owner $owner -Trigger $trigger -Status $status)) } } function ConvertFrom-AdvisoryDecisionSlate { <# .SYNOPSIS Binds a decision slate to the open findings on an advisory record. .DESCRIPTION `3 Fix owner spike-runner` records Fix with that next owner. The selected work destination is the finding's route. A numbered Fix that names no owner records the route's default owner when the route has one. A numbered Fix on a route with no default owner is not bound, and its finding id is returned in needs_owner. `3 Fix owner` with the name omitted is the same refusal, even when the route has a default owner. A named owner on specification must be spec-writer, and a named owner on domain-design must be ddd-architect. Investigation and deferred-work keep the named owner. `all Fix` records Fix only for open findings whose route has a default owner. Skipped finding ids are listed in needs_owner and stay open. #> param( [Parameter(Mandatory = $true)][string]$SlateReply, [Parameter(Mandatory = $true)]$Assessment ) $findings = @((Get-AdvisoryWorkflowProperty -Record $Assessment -Name 'findings')) $lines = @($SlateReply -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) $bound = [System.Collections.Generic.List[object]]::new() $needsDirection = [System.Collections.Generic.List[object]]::new() $needsOwner = [System.Collections.Generic.List[string]]::new() $expandAllFix = $false foreach ($line in $lines) { if ($line -match '(?i)^all\s+Fix$') { $expandAllFix = $true; break } } if ($expandAllFix) { Add-AdvisorySlateAllFixRow -Findings $findings -Bound $bound -NeedsOwner $needsOwner } else { Add-AdvisorySlateNumberedRow -Lines $lines -Findings $findings -Bound $bound -NeedsDirection $needsDirection -NeedsOwner $needsOwner } return New-AdvisorySlateBindResult -Bound $bound -NeedsDirection $needsDirection -NeedsOwner $needsOwner } |