Isolate scratch runner setup from strict mode

This commit is contained in:
master
2026-03-13 02:50:54 +02:00
parent 27d0247058
commit 407ab84cbb

View File

@@ -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
}