scripts/internal/continuous-co-review/tracker-honesty-check.ps1
|
# Tracker honesty check (F-198 FR-020 / proposal 203-W13, design mechanism b). # # The digest identity formula is UNCHANGED - trackers stay identity. This module lets the # signoff gate ACCEPT a passing run's evidence as fresh when the only delta between the # reviewed tree and the current tree is machine-managed tracker bookkeeping whose claims are # consistent with (a subset of) the review record that run already accepted. The bypass is a # gate-level, ANNOUNCED decision - never a change to what gets digested. # # Fail direction (binding, I3): any parse ambiguity, any unknown file shape, any claim the # check cannot map to the accepted record -> NOT honest -> the digest stales exactly as # today (costs at worst one re-review; fail-open here would be a false-green door). Set-StrictMode -Version Latest $script:TrackerPathPattern = '^specs/[^/]+/iterations/[^/]+/(state\.md|tasks-progress\.yml)$' function Get-ContinuousCoReviewTrackerOnlyDelta { # The name-only delta between two REVIEWED trees (git tree objects persist, so both sides # are always materializable). TrackerOnly is true only when every changed path is a # machine-managed tracker AND at least one path changed (identical-looking ids with an # empty visible delta are suspicious -> not tracker-only, fail-closed). param( [Parameter(Mandatory)][string]$RepoRoot, [Parameter(Mandatory)][string]$FromTreeId, [Parameter(Mandatory)][string]$ToTreeId ) $result = Invoke-ContinuousCoReviewGit -RepoRoot $RepoRoot -Arguments @('diff', '--name-only', $FromTreeId, $ToTreeId) if ($result.ExitCode -ne 0) { return [pscustomobject]@{ Ok = $false; TrackerOnly = $false; Paths = @(); Reason = 'delta-unresolvable' } } $paths = @($result.Output | Where-Object { $_ -and ($_ -notmatch '^(fatal|error):') }) if ($paths.Count -eq 0) { return [pscustomobject]@{ Ok = $true; TrackerOnly = $false; Paths = @(); Reason = 'empty-delta' } } $trackerOnly = $true foreach ($p in $paths) { if (($p -replace '\\', '/') -notmatch $script:TrackerPathPattern) { $trackerOnly = $false; break } } [pscustomobject]@{ Ok = $true; TrackerOnly = $trackerOnly; Paths = $paths; Reason = $(if ($trackerOnly) { 'tracker-only' } else { 'non-tracker-paths' }) } } function Get-ContinuousCoReviewTreeFileContent { param( [Parameter(Mandatory)][string]$RepoRoot, [Parameter(Mandatory)][string]$TreeId, [Parameter(Mandatory)][string]$Path ) $result = Invoke-ContinuousCoReviewGit -RepoRoot $RepoRoot -Arguments @('show', ("{0}:{1}" -f $TreeId, (($Path) -replace '\\', '/'))) if ($result.ExitCode -ne 0) { return $null } return ($result.Output -join "`n") } function Get-ContinuousCoReviewStateClaims { # Parse the CLAIM-bearing fields of a state.md against canonical enums/shapes (the # TrackerClaims data model, FR-020). Returns $null (fail-closed at the caller) when ANY # claim-bearing field is present in a non-canonical or unrecognized form. Extract-and-ignore # would be a false-green door (the I3 fail-direction: an unmapped/unknown claim must DECLINE # the bypass, never pass unchecked). The completion-signalling fields are Iteration Status and # Last Completed Task; capacity/test-count claims belong to plan.md / coverage-evidence.md # (non-tracker paths whose change already declines), so their appearance HERE is an # unreconcilable injected claim and fails closed. param([AllowNull()][string]$Content) if ([string]::IsNullOrWhiteSpace($Content)) { return $null } # Iteration Status: when present it MUST be a canonical iteration-status enum; an unknown # status form (e.g. a completion-implying synonym) fails closed instead of passing unchecked. $status = $null if ($Content -match '(?m)^\*\*Iteration Status\*\*:\s*(?<v>\S[^\r\n]*?)\s*$') { $raw = $Matches['v'].Trim() if ($raw -notin @('planning', 'executing', 'reviewing', 'retro', 'complete', 'abandoned')) { return $null } $status = $raw } # Last Completed Task: when present it MUST be a task id (Tnnn...) or an explicit '(none...)' # sentinel; any other free-text claim shape fails closed. $lastTask = $null if ($Content -match '(?m)^\*\*Last Completed Task\*\*:\s*(?<v>\S[^\r\n]*?)\s*$') { $raw = $Matches['v'].Trim() if ($raw -match '^T\d{3}\b' -or $raw -match '^\(none') { $lastTask = $raw } else { return $null } } # Capacity and test-count claims are authoritative TrackerClaims but their homes are plan.md / # coverage-evidence.md (non-tracker paths). Injected into a tracker file they cannot be # reconciled against the accepted review record -> fail closed. if ($Content -match '(?im)^\**\s*Capacity\b' -or $Content -match '(?im)^\**\s*Tests?\s+(Run|Count|Total|Passed)\b') { return $null } if ($null -eq $status -and $null -eq $lastTask) { return $null } [pscustomobject]@{ IterationStatus = $status; LastCompletedTask = $lastTask } } function Get-ContinuousCoReviewAcceptedReviewRecord { # The accepted review record FROM THE REVIEWED TREE: overall verdict + per-task verdicts. # $null when review.md is absent or unparseable (fail-closed at the caller). param( [Parameter(Mandatory)][string]$RepoRoot, [Parameter(Mandatory)][string]$TreeId, [Parameter(Mandatory)][string]$IterationDirRelative ) $reviewPath = ("{0}/review.md" -f ($IterationDirRelative -replace '\\', '/')).TrimStart('/') $content = Get-ContinuousCoReviewTreeFileContent -RepoRoot $RepoRoot -TreeId $TreeId -Path $reviewPath if ([string]::IsNullOrWhiteSpace($content)) { return $null } $overall = if ($content -match '(?m)^\*\*Overall Verdict\*\*:\s*(?<v>[a-z-]+)\s*$') { $Matches['v'] } else { $null } if ($null -eq $overall) { return $null } $taskVerdicts = @{} foreach ($m in [regex]::Matches($content, '(?m)^\|\s*(?<task>T\d{3})\s*\|[^|]*\|\s*(?<verdict>pass|needs-work|locked)\s*\|')) { $taskVerdicts[$m.Groups['task'].Value] = $m.Groups['verdict'].Value } [pscustomobject]@{ OverallVerdict = $overall; TaskVerdicts = $taskVerdicts } } function Test-ContinuousCoReviewTrackerReconcileHonest { # THE honesty decision for one tracker-only delta: every claiming change in the new # tracker content must be covered by the accepted review record already inside the # reviewed tree. Claim-INCREASING edits (done/complete beyond the accepted record) and # every parse ambiguity return Honest=$false. param( [Parameter(Mandatory)][string]$RepoRoot, [Parameter(Mandatory)][string]$FromTreeId, [Parameter(Mandatory)][string]$ToTreeId, [Parameter(Mandatory)][string[]]$TrackerPaths ) foreach ($path in $TrackerPaths) { $normalized = $path -replace '\\', '/' if ($normalized -notmatch $script:TrackerPathPattern) { return [pscustomobject]@{ Honest = $false; Reason = ("non-tracker path '{0}' in a tracker-only check" -f $normalized) } } $iterationDir = $normalized -replace '/(state\.md|tasks-progress\.yml)$', '' $accepted = Get-ContinuousCoReviewAcceptedReviewRecord -RepoRoot $RepoRoot -TreeId $FromTreeId -IterationDirRelative $iterationDir if ($null -eq $accepted) { return [pscustomobject]@{ Honest = $false; Reason = ("no parseable accepted review record beside '{0}' in the reviewed tree" -f $normalized) } } if ($normalized -like '*state.md') { $oldClaims = Get-ContinuousCoReviewStateClaims -Content (Get-ContinuousCoReviewTreeFileContent -RepoRoot $RepoRoot -TreeId $FromTreeId -Path $normalized) $newClaims = Get-ContinuousCoReviewStateClaims -Content (Get-ContinuousCoReviewTreeFileContent -RepoRoot $RepoRoot -TreeId $ToTreeId -Path $normalized) if ($null -eq $newClaims) { return [pscustomobject]@{ Honest = $false; Reason = ("unparseable new state claims in '{0}'" -f $normalized) } } # Iteration Status: 'complete' is a claim - it needs the accepted overall verdict. if ([string]$newClaims.IterationStatus -eq 'complete' -and [string]$accepted.OverallVerdict -ne 'accepted') { return [pscustomobject]@{ Honest = $false; Reason = 'state.md claims complete but the accepted review verdict is not accepted' } } # Last Completed Task: claiming a NEW task needs that task's pass verdict. $newTask = [string]$newClaims.LastCompletedTask $oldTask = if ($null -ne $oldClaims) { [string]$oldClaims.LastCompletedTask } else { '' } if ($newTask -match '^(?<id>T\d{3})' -and $newTask -ne $oldTask) { $taskId = $Matches['id'] if (-not $accepted.TaskVerdicts.ContainsKey($taskId) -or [string]$accepted.TaskVerdicts[$taskId] -ne 'pass') { return [pscustomobject]@{ Honest = $false; Reason = ("state.md claims '{0}' completed but the accepted review has no pass verdict for it" -f $taskId) } } } continue } # tasks-progress.yml: canonical simple map lines `Tnnn: <status>`; anything else is # ambiguous -> fail-closed. A task newly claimed 'done' needs its accepted pass verdict. $oldContent = Get-ContinuousCoReviewTreeFileContent -RepoRoot $RepoRoot -TreeId $FromTreeId -Path $normalized $newContent = Get-ContinuousCoReviewTreeFileContent -RepoRoot $RepoRoot -TreeId $ToTreeId -Path $normalized if ([string]::IsNullOrWhiteSpace($newContent)) { return [pscustomobject]@{ Honest = $false; Reason = ("unreadable new tracker content '{0}'" -f $normalized) } } $parseMap = { param($content) $map = @{} foreach ($line in ($content -split "`r?`n")) { if ([string]::IsNullOrWhiteSpace($line) -or $line.TrimStart().StartsWith('#')) { continue } if ($line -match '^\s*(?<task>T\d{3})\s*:\s*(?<status>[a-z-]+)\s*$') { # task_statuses: canonical enums only (data model). A non-canonical status # form fails closed rather than being accepted as an unknown claim. $st = $Matches['status'] if ($st -notin @('planned', 'in-progress', 'done', 'needs-rework', 'deferred', 'blocked')) { return $null } $map[$Matches['task']] = $st } else { return $null } } $map } $newMap = & $parseMap $newContent if ($null -eq $newMap) { return [pscustomobject]@{ Honest = $false; Reason = ("non-canonical tasks-progress shape in '{0}'" -f $normalized) } } $oldMap = if ([string]::IsNullOrWhiteSpace($oldContent)) { @{} } else { & $parseMap $oldContent } if ($null -eq $oldMap) { $oldMap = @{} } foreach ($taskId in $newMap.Keys) { if ([string]$newMap[$taskId] -ne 'done') { continue } if ($oldMap.ContainsKey($taskId) -and [string]$oldMap[$taskId] -eq 'done') { continue } if (-not $accepted.TaskVerdicts.ContainsKey($taskId) -or [string]$accepted.TaskVerdicts[$taskId] -ne 'pass') { return [pscustomobject]@{ Honest = $false; Reason = ("tasks-progress claims '{0}' done but the accepted review has no pass verdict for it" -f $taskId) } } } } [pscustomobject]@{ Honest = $true; Reason = 'claims are a subset of the accepted review record' } } |