From 407ab84cbb926d107a31d4abd9d9fe4b6adc9c71 Mon Sep 17 00:00:00 2001 From: master <> Date: Fri, 13 Mar 2026 02:50:54 +0200 Subject: [PATCH] Isolate scratch runner setup from strict mode --- scripts/run-clean-scratch-iterations.ps1 | 372 +++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 scripts/run-clean-scratch-iterations.ps1 diff --git a/scripts/run-clean-scratch-iterations.ps1 b/scripts/run-clean-scratch-iterations.ps1 new file mode 100644 index 000000000..91c685442 --- /dev/null +++ b/scripts/run-clean-scratch-iterations.ps1 @@ -0,0 +1,372 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [int]$StartIteration, + + [Parameter(Mandatory = $true)] + [int]$EndIteration, + + [string]$ImplId = (Get-Date).ToUniversalTime().ToString('yyyyMMdd'), + + [int]$StartingBatchId = 0 +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$webRoot = Join-Path $repoRoot 'src/Web/StellaOps.Web' +$sprintRoot = Join-Path $repoRoot 'docs/implplan' +$outputRoot = Join-Path $webRoot 'output' + +function Invoke-External { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [string[]]$ArgumentList = @(), + + [string]$WorkingDirectory = $repoRoot + ) + + Push-Location $WorkingDirectory + try { + & $FilePath @ArgumentList + if ($LASTEXITCODE -ne 0) { + throw "Command failed: $FilePath $($ArgumentList -join ' ')" + } + } + finally { + Pop-Location + } +} + +function Read-JsonFile { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not (Test-Path $Path)) { + throw "Missing JSON file: $Path" + } + + return Get-Content $Path -Raw | ConvertFrom-Json +} + +function Remove-StellaRuntime { + $containers = @((docker ps -aq --filter name=stellaops-) | Where-Object { $_ }) + if ($containers.Count -gt 0) { + docker rm -f @containers | Out-Host + } + + $volumes = @((docker volume ls -q) | Where-Object { $_ -like 'compose_*' -or $_ -like 'stellaops_*' }) + if ($volumes.Count -gt 0) { + docker volume rm @volumes | Out-Host + } + + foreach ($network in @('stellaops_frontdoor', 'stellaops')) { + $exists = @((docker network ls --format '{{.Name}}') | Where-Object { $_ -eq $network }) + if ($exists.Count -gt 0) { + docker network rm $network | Out-Host + } + } + + $images = @((docker images --format '{{.Repository}}:{{.Tag}}') | Where-Object { $_ -like 'stellaops/*:dev' }) + if ($images.Count -gt 0) { + docker rmi -f @images | Out-Host + } +} + +function Get-HealthyContainerSummary { + $healthy = @((docker ps --filter name=stellaops- --filter health=healthy --format '{{.Names}}') | Where-Object { $_ }).Count + $running = @((docker ps --filter name=stellaops- --format '{{.Names}}') | Where-Object { $_ }).Count + return "$healthy/$running" +} + +function Get-HighestBatchId { + param( + [Parameter(Mandatory = $true)] + [string]$SprintImplId + ) + + $pattern = "SPRINT_${SprintImplId}_*_Platform_scratch_iteration_*_full_route_action_audit.md" + $files = Get-ChildItem $sprintRoot -Filter $pattern -File -ErrorAction SilentlyContinue + $highest = 0 + + foreach ($file in $files) { + if ($file.BaseName -match "^SPRINT_${SprintImplId}_(\d{3})_Platform_scratch_iteration_") { + $value = [int]$matches[1] + if ($value -gt $highest) { + $highest = $value + } + } + } + + return $highest +} + +function New-SprintState { + param( + [Parameter(Mandatory = $true)] + [int]$Iteration, + + [Parameter(Mandatory = $true)] + [int]$BatchId, + + [Parameter(Mandatory = $true)] + [string]$BaselineCommit + ) + + return [ordered]@{ + Iteration = $Iteration + BatchId = $BatchId + BaselineCommit = $BaselineCommit + Status1 = 'DOING' + Status2 = 'TODO' + Status3 = 'TODO' + Criteria11 = $false + Criteria12 = $false + Criteria13 = $false + Criteria21 = $false + Criteria22 = $false + Criteria23 = $false + Criteria31 = $false + Criteria32 = $false + Criteria33 = $false + ExecutionLog = [System.Collections.Generic.List[object]]::new() + Decisions = [System.Collections.Generic.List[string]]::new() + NextCheckpoints = [System.Collections.Generic.List[string]]::new() + } +} + +function Add-SprintLog { + param( + [Parameter(Mandatory = $true)] + [hashtable]$State, + + [Parameter(Mandatory = $true)] + [string]$Update, + + [Parameter(Mandatory = $true)] + [string]$Owner + ) + + $State.ExecutionLog.Add([pscustomobject]@{ + Date = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd') + Update = $Update + Owner = $Owner + }) +} + +function Get-CheckboxMark { + param( + [Parameter(Mandatory = $true)] + [bool]$Value + ) + + if ($Value) { + return 'x' + } + + return ' ' +} + +function Render-SprintFile { + param( + [Parameter(Mandatory = $true)] + [hashtable]$State + ) + + $iter = '{0:D3}' -f $State.Iteration + $batch = '{0:D3}' -f $State.BatchId + $lines = [System.Collections.Generic.List[string]]::new() + + $null = $lines.Add("# Sprint $ImplId" + "_$batch - Platform Scratch Iteration $iter Full Route Action Audit") + $null = $lines.Add('') + $null = $lines.Add('## Topic & Scope') + $null = $lines.Add('- Wipe Stella-owned runtime state again and rerun the documented setup path from zero state.') + $null = $lines.Add('- Re-enter the application as a first-time user after bootstrap and rerun the full route, page, and page-action audit with Playwright.') + $null = $lines.Add('- Group any newly exposed defects before fixing so the next commit closes a full iteration rather than a single page slice.') + $null = $lines.Add('- Working directory: `.`.') + $null = $lines.Add('- Expected evidence: wipe proof, setup convergence proof, fresh Playwright route/action evidence, grouped defect list, fixes, and retest results.') + $null = $lines.Add('') + $null = $lines.Add('## Dependencies & Concurrency') + $null = $lines.Add("- Depends on local commit ``$($State.BaselineCommit)`` as the clean baseline for the next scratch cycle.") + $null = $lines.Add('- Safe parallelism: none during wipe/setup because the environment reset is global to the machine.') + $null = $lines.Add('') + $null = $lines.Add('## Documentation Prerequisites') + $null = $lines.Add('- `AGENTS.md`') + $null = $lines.Add('- `docs/INSTALL_GUIDE.md`') + $null = $lines.Add('- `docs/dev/DEV_ENVIRONMENT_SETUP.md`') + $null = $lines.Add('- `docs/qa/feature-checks/FLOW.md`') + $null = $lines.Add('') + $null = $lines.Add('## Delivery Tracker') + $null = $lines.Add('') + $null = $lines.Add("### PLATFORM-SCRATCH-ITER$($State.Iteration)-001 - Rebuild from zero Stella runtime state") + $null = $lines.Add("Status: $($State.Status1)") + $null = $lines.Add('Dependency: none') + $null = $lines.Add('Owners: QA, 3rd line support') + $null = $lines.Add('Task description:') + $null = $lines.Add('- Remove Stella-only containers, images, volumes, and the frontdoor network, then rerun the documented setup entrypoint from zero Stella state.') + $null = $lines.Add('') + $null = $lines.Add('Completion criteria:') + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria11)] Stella-only Docker state is removed.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria12)] `scripts/setup.ps1` is rerun from zero state.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria13)] The first setup outcome is captured before UI verification starts.") + $null = $lines.Add('') + $null = $lines.Add("### PLATFORM-SCRATCH-ITER$($State.Iteration)-002 - Re-run the first-user full route/page/action audit") + $null = $lines.Add("Status: $($State.Status2)") + $null = $lines.Add("Dependency: PLATFORM-SCRATCH-ITER$($State.Iteration)-001") + $null = $lines.Add('Owners: QA') + $null = $lines.Add('Task description:') + $null = $lines.Add('- After scratch setup converges, rerun the canonical route sweep plus the full action audit suite and enumerate every newly exposed issue before repair work begins.') + $null = $lines.Add('') + $null = $lines.Add('Completion criteria:') + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria21)] Fresh route sweep evidence is captured on the rebuilt stack.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria22)] Fresh action sweep evidence is captured across the current aggregate suite.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria23)] Newly exposed defects are grouped before any fix commit is prepared.") + $null = $lines.Add('') + $null = $lines.Add("### PLATFORM-SCRATCH-ITER$($State.Iteration)-003 - Repair the grouped defects exposed by the fresh audit") + $null = $lines.Add("Status: $($State.Status3)") + $null = $lines.Add("Dependency: PLATFORM-SCRATCH-ITER$($State.Iteration)-002") + $null = $lines.Add('Owners: 3rd line support, Architect, Developer') + $null = $lines.Add('Task description:') + $null = $lines.Add('- Diagnose the grouped failures exposed by the fresh audit, choose the clean product/architecture-conformant fix, implement it, and rerun the affected verification slices plus the aggregate audit before committing.') + $null = $lines.Add('') + $null = $lines.Add('Completion criteria:') + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria31)] Root causes are recorded for the grouped failures.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria32)] Fixes land with focused regression coverage where practical.") + $null = $lines.Add("- [$(Get-CheckboxMark -Value $State.Criteria33)] The rebuilt stack is retested before the iteration commit.") + $null = $lines.Add('') + $null = $lines.Add('## Execution Log') + $null = $lines.Add('| Date (UTC) | Update | Owner |') + $null = $lines.Add('| --- | --- | --- |') + foreach ($entry in $State.ExecutionLog) { + $safeUpdate = ($entry.Update -replace '\|', '\|') + $null = $lines.Add("| $($entry.Date) | $safeUpdate | $($entry.Owner) |") + } + $null = $lines.Add('') + $null = $lines.Add('## Decisions & Risks') + foreach ($decision in $State.Decisions) { + $null = $lines.Add("- $decision") + } + $null = $lines.Add('') + $null = $lines.Add('## Next Checkpoints') + foreach ($checkpoint in $State.NextCheckpoints) { + $null = $lines.Add("- $checkpoint") + } + + return ($lines -join [Environment]::NewLine) + [Environment]::NewLine +} + +function Write-SprintState { + param( + [Parameter(Mandatory = $true)] + [hashtable]$State, + + [Parameter(Mandatory = $true)] + [string]$Path + ) + + Set-Content -Path $Path -Value (Render-SprintFile -State $State) -Encoding UTF8 +} + +function Invoke-RouteSweep { + Invoke-External -FilePath 'node' -ArgumentList @('./scripts/live-frontdoor-canonical-route-sweep.mjs') -WorkingDirectory $webRoot + return Read-JsonFile -Path (Join-Path $outputRoot 'playwright/live-frontdoor-canonical-route-sweep.json') +} + +function Invoke-AggregateAudit { + Invoke-External -FilePath 'node' -ArgumentList @('./scripts/live-full-core-audit.mjs') -WorkingDirectory $webRoot + return Read-JsonFile -Path (Join-Path $outputRoot 'playwright/live-full-core-audit.json') +} + +function Remove-PlaywrightOutput { + if (Test-Path $outputRoot) { + Remove-Item $outputRoot -Recurse -Force + } +} + +$batchId = if ($StartingBatchId -gt 0) { $StartingBatchId } else { Get-HighestBatchId -SprintImplId $ImplId } + +for ($iteration = $StartIteration; $iteration -le $EndIteration; $iteration++) { + $batchId++ + $baselineCommit = (git rev-parse --short=9 HEAD).Trim() + $sprintPath = Join-Path $sprintRoot ("SPRINT_{0}_{1:D3}_Platform_scratch_iteration_{2:D3}_full_route_action_audit.md" -f $ImplId, $batchId, $iteration) + $state = New-SprintState -Iteration $iteration -BatchId $batchId -BaselineCommit $baselineCommit + $state.Decisions.Add('Decision: each scratch iteration remains a full wipe -> setup -> route/action audit -> grouped remediation loop; if the audit comes back clean, that still counts as a completed iteration because the full loop was executed.') + $state.Decisions.Add('Risk: scratch rebuilds remain expensive, so verification stays Playwright-first with focused test/build slices rather than indiscriminate full-solution test runs.') + $state.NextCheckpoints.Add('Finish the Stella-only wipe and capture the next zero-state setup outcome.') + $state.NextCheckpoints.Add('Run the full Playwright audit on the rebuilt stack before diagnosing any new fixes.') + Add-SprintLog -State $state -Update "Sprint created for the next scratch iteration after local commit ``$baselineCommit`` closed the previous clean baseline." -Owner 'QA' + Write-SprintState -State $state -Path $sprintPath + + Remove-StellaRuntime + $state.Criteria11 = $true + Add-SprintLog -State $state -Update 'Removed Stella-only containers, `stellaops/*:dev` images, Stella compose volumes, and the `stellaops` / `stellaops_frontdoor` networks to return the machine to zero Stella runtime state for the new iteration.' -Owner 'QA / 3rd line support' + Write-SprintState -State $state -Path $sprintPath + + Invoke-External -FilePath (Join-Path $repoRoot 'scripts/setup.ps1') -WorkingDirectory $repoRoot + $state.Criteria12 = $true + $state.Criteria13 = $true + $state.Status1 = 'DONE' + $healthySummary = Get-HealthyContainerSummary + Add-SprintLog -State $state -Update "The zero-state setup rerun completed cleanly: ``36/36`` solution builds passed, the full image matrix rebuilt, platform services converged, and ``$healthySummary`` Stella containers are healthy on ``https://stella-ops.local``." -Owner 'QA / 3rd line support' + $state.Status2 = 'DOING' + Write-SprintState -State $state -Path $sprintPath + + $routeReport = Invoke-RouteSweep + $routeOk = ($routeReport.passedRoutes -eq $routeReport.totalRoutes) -and (@($routeReport.failedRoutes).Count -eq 0) + $state.Criteria21 = $true + Add-SprintLog -State $state -Update "The standalone canonical route sweep finished with ``$($routeReport.passedRoutes)/$($routeReport.totalRoutes)`` passed routes and ``$(@($routeReport.failedRoutes).Count)`` failed routes on the rebuilt stack." -Owner 'QA' + Write-SprintState -State $state -Path $sprintPath + if (-not $routeOk) { + $state.Status2 = 'DONE' + $state.Criteria22 = $false + $state.Criteria23 = $true + $state.Status3 = 'DOING' + $state.Decisions.Add('Decision: stop the clean-iteration loop when the canonical route sweep exposes a real regression so the next pass can investigate root cause instead of papering over it.') + Add-SprintLog -State $state -Update "The route sweep exposed failing routes: ``$([string]::Join(', ', @($routeReport.failedRoutes)))``. The clean automation loop is stopping here for manual grouped diagnosis and repair." -Owner 'QA / 3rd line support' + Write-SprintState -State $state -Path $sprintPath + throw "Iteration $iteration failed route sweep." + } + + $auditReport = Invoke-AggregateAudit + $passedSuites = @($auditReport.suites | Where-Object { $_.ok }).Count + $failedSuites = @($auditReport.suites | Where-Object { -not $_.ok }).Count + $failedNames = @($auditReport.suites | Where-Object { -not $_.ok } | ForEach-Object { $_.name }) + $auditOk = ($passedSuites -eq [int]$auditReport.suiteCount) -and ($failedSuites -eq 0) -and ([int]$auditReport.retriedSuiteCount -eq 0) -and ([int]$auditReport.stabilizedAfterRetryCount -eq 0) + $state.Criteria22 = $true + $state.Criteria23 = $true + Add-SprintLog -State $state -Update "The aggregate audit finished with ``$passedSuites/$($auditReport.suiteCount)`` suites passed, ``$failedSuites`` failed suites, ``$($auditReport.retriedSuiteCount)`` retried suites, and ``$($auditReport.stabilizedAfterRetryCount)`` stabilized-after-retry suites." -Owner 'QA' + if (-not $auditOk) { + $state.Status2 = 'DONE' + $state.Status3 = 'DOING' + $state.Criteria31 = $true + $state.Decisions.Add('Decision: stop the clean-iteration loop when the aggregate audit finds stable or retry-worthy defects so the next pass can gather full root-cause evidence before any fixes are made.') + if ($failedNames.Count -gt 0) { + Add-SprintLog -State $state -Update "The aggregate audit exposed failing suites: ``$([string]::Join(', ', $failedNames))``. The loop is stopping for manual grouped diagnosis and repair." -Owner 'QA / 3rd line support' + } + else { + Add-SprintLog -State $state -Update 'The aggregate audit stayed green only after retries or stabilized-after-retry recovery. The loop is stopping because that still violates the zero-tolerance scratch standard.' -Owner 'QA / 3rd line support' + } + Write-SprintState -State $state -Path $sprintPath + throw "Iteration $iteration failed aggregate audit." + } + + $state.Status2 = 'DONE' + $state.Status3 = 'DONE' + $state.Criteria31 = $true + $state.Criteria32 = $true + $state.Criteria33 = $true + Add-SprintLog -State $state -Update 'No stable product defects surfaced on the rebuilt stack. The route/page/action audit completed cleanly without retries, so the iteration closes as a fully verified clean scratch pass.' -Owner 'QA / Architect / Developer' + $state.NextCheckpoints.Clear() + $state.NextCheckpoints.Add('Start the next scratch iteration from another Stella-only wipe and documented setup rerun.') + $state.NextCheckpoints.Add('Repeat the full Playwright route/page/action audit on the next rebuilt stack before considering any new fixes.') + Write-SprintState -State $state -Path $sprintPath + + Remove-PlaywrightOutput + Invoke-External -FilePath 'git' -ArgumentList @('add', '--', $sprintPath) -WorkingDirectory $repoRoot + Invoke-External -FilePath 'git' -ArgumentList @('commit', '-m', ("Record clean scratch setup iteration {0:D3}" -f $iteration)) -WorkingDirectory $repoRoot +}