ci: add bounded complete release gate

This commit is contained in:
Erik 2026-08-18 09:09:38 +02:00
parent 0a934cf578
commit 2ac054864d
11 changed files with 772 additions and 102 deletions

View file

@ -419,9 +419,9 @@ checkpoint. Do not infer status from chat history.
| Slice | Status | Commit(s) | Evidence/gates | Exact next action |
|---|---|---|---|---|
| R0 | PLAN ACCEPTED; tracking/owner assignment pending | — | Audit complete at `15539a22`; user started R1 | Track plan+audit artifacts; assign R4 owner |
| R1 | IMPLEMENTED + REQUIRED GATES PASS; review/commit pending | — | 2026-08-18 checkpoint below; F-009/T-001 resolved in working tree | Review diff, then commit the coherent R1 checkpoint |
| R2 | NOT STARTED; technically unblocked after R1 review/commit | — | F-010/F-014/F-019 | Start only after R1 checkpoint is accepted |
| R3 | NOT STARTED | — | T-002T-016; F-015/F-021/F-022/F-030 | Wait for R2 |
| R1 | COMPLETE ON CAMPAIGN BRANCH; not yet merged | `0a934cf5` | 2026-08-18 checkpoint below; F-009/T-001 resolved and committed | Preserve while R2 runs; merge through the normal review path |
| R2 | COMPLETE GATE CHECKPOINT; broader reproducibility work remains; commit pending | — | Checkpoint below; F-014 resolved, F-010/F-019 partially resolved | Review and commit this checkpoint, then choose locked restore/warning work or R3 |
| R3 | NOT STARTED | — | T-002T-018; F-015/F-021/F-022/F-030 | Wait for remaining R2 sequencing decision |
| R4 | NOT STARTED; may run parallel | — | F-001/F-026/F-031/F-034 | Assign owner/legal provenance decision path |
| R5 | NOT STARTED | — | F-002/F-003/F-006/F-007/F-011/F-020/F-027/F-029/F-032 | Wait for R2R3 |
| R6 | NOT STARTED | — | F-004/F-005/F-012/F-016/F-023/F-028 | Wait for R3/R5 |
@ -433,8 +433,8 @@ checkpoint. Do not infer status from chat history.
### R1 implementation checkpoint — 2026-08-18
**Working-tree base:** `15539a22a67f8d915d88f8b1d8126cd55eedda6e`
**Commit:** pending; do not describe this checkpoint as merged or durable until
the source, test, plan, and audit records are committed together.
**Commit:** `0a934cf5` on `codex/release-stabilization`; the checkpoint is
durable on that campaign branch but is not yet merged to `main`.
Implementation:
@ -495,9 +495,79 @@ Changed implementation/test files:
- `src/AcDream.Launcher.Core/Launching/LauncherProcessSupervisor.cs`
- `tests/AcDream.Launcher.Core.Tests/Launching/LauncherProcessSupervisorTests.cs`
Rollback before commit is the exact reverse of those two file diffs. After a
future commit, record its hash here and use `git revert <R1-commit>` rather than
rewriting history.
Rollback is `git revert 0a934cf5`; do not rewrite branch history.
### R2 complete-gate checkpoint — 2026-08-18
**Working-tree base:** `0a934cf5781c003375c14af9a1f565254df0f9f9`
**Commit:** pending; the source and evidence records must be committed together
before this checkpoint is described as durable.
Implemented gate:
- `global.json` pins the accepted .NET 10 SDK feature band at `10.0.300` with
`latestPatch` roll-forward and prerelease SDKs disabled. Every existing
`actions/setup-dotnet` step now reads that file instead of floating on
`10.0.x`.
- `tools/run-release-gate.ps1` discovers every project under `tests/` that
declares `Microsoft.NET.Test.Sdk` or `IsTestProject`, verifies the project is
present in `AcDream.slnx`, restores/builds the solution, and runs each test
assembly exactly once in its own Release process. It does not retry.
- Restore, build, and each test process have 600/900/600-second outer bounds.
Tests additionally use VSTest's 180-second per-test blame-hang collector with
mini dumps. An outer timeout kills the complete process tree and reports exit
code 124; GitHub Actions adds a 45-minute job bound.
- Every run writes exact commands and output, one TRX per assembly, any blame
sequence/dumps, `dotnet --info`, configured NuGet sources, commit/branch/RID,
aggregate executed/passed/skipped/failed counts, and `SHA256SUMS.txt`.
- `.github/workflows/release-gate.yml` runs the gate on pull requests, pushes
to `main`, and manual dispatch on `windows-latest`, then uploads the evidence
even when the gate fails. The focused Windows/Linux portability and Vulkan
lanes remain separate and are no longer the only deterministic CI coverage.
- `docs/release-gate.md` is the repository-owned local/CI runbook.
Test-isolation corrections, with no product behavior change:
- Four `MainWindowViewTests` that call `Show()` now close their window and pump
dispatcher cleanup in `finally` on the owning Avalonia test session. The old
tests leaked shown, thread-affine compositor state to runner teardown; no
suite serialization or retry was added.
- The complete gate's first evidence run correctly failed
`RealChildStderrIsCapturedForTheProcessStartInfoPath`: its live polling helper
briefly denied write sharing, so the final async stderr callback observed an
`IOException` and the deliberately no-throw capture sink latched off. The
helper now reads with `FileShare.ReadWrite | FileShare.Delete`, matching the
production status tailer; its assertions and five-second bound are unchanged.
Verification:
- Focused `MainWindowViewTests`: 13 passed / 0 skipped / 0 failed.
- Three fresh default-parallel whole-solution runs completed inside independent
180-second process bounds with 12/12 TRX files and no Avalonia cleanup error:
56.719, 56.321, and 58.506 seconds. Each reported 14,748 passed / 77 skipped /
0 failed.
- The stderr ProcessStartInfo test passed 25/25 fresh-process repetitions after
the live-reader correction.
- The actual outer-watchdog function killed a controlled fixture process tree
at 2.107 seconds, returned 124, and left no child process.
- The repository command `pwsh ./tools/run-release-gate.ps1` completed in
107.337 seconds on SDK `10.0.300`, RID `win-x64`: 12 assemblies, 14,748
executed and passed / 77 skipped / 0 failed. The evidence manifest contains
28 verified SHA-256 entries.
Deliberately still open in the wider R2 slice:
- the 26 warnings observed by a clean recompilation, centralized compiler and
package settings, package lock files/locked restore, and broken or unsupported
tool-project decisions;
- the known duplicate Core theory row and classification of the 77 skips, which
remain R3 work; and
- stale public headline counts, which must be corrected with the documentation
authority work rather than hand-edited as part of this gate checkpoint.
Therefore this checkpoint closes F-014 on the campaign branch and the SDK part
of F-019, and gives F-010 a truthful machine-readable count. It does not claim
the broader R2 reproducibility slice or R3 test-quality cleanup is complete.
## 18. Session start protocol
@ -532,7 +602,7 @@ review, and cross-session ledger entry are all complete.
## 20. Immediate next action
Review the R1 diff and checkpoint evidence, then commit the source, regression
test, plan, and audit updates together if accepted. After that, begin R2's
pinned complete gate; assign the R4 decision owner in parallel. Do not begin
bulk comment, artifact, or giant-file cleanup first.
Review and commit the R2 complete-gate checkpoint, then decide whether the next
bounded goal is the remaining R2 locked-restore/warning/toolchain work or R3's
truthful test taxonomy. Assign the R4 decision owner in parallel. Do not begin
bulk comment, artifact, giant-file, or unrelated cleanup first.

41
docs/release-gate.md Normal file
View file

@ -0,0 +1,41 @@
# Complete Release gate
The default release gate is repository-owned and uses the SDK feature band in
`global.json`:
```powershell
pwsh ./tools/run-release-gate.ps1
```
The command restores and builds `AcDream.slnx`, discovers every project under
`tests/` that declares `Microsoft.NET.Test.Sdk` or `IsTestProject`, verifies
that the solution includes it, and runs each test assembly once in a fresh
Release process. It does not retry failures.
Each restore, build, and test process has an outer hard timeout. Every test
also runs with VSTest blame-hang enabled: after three minutes in one test, the
test host is terminated and a mini dump is collected; after ten minutes, the
outer watchdog kills the complete `dotnet test` process tree. CI additionally
has a 45-minute job bound.
Evidence is written to `artifacts/release-gate/`:
- `release-gate-summary.json` records the commit, branch, worktree state, SDK,
RID, bounds, process outcomes, assembly list, and
executed/passed/skipped/failed totals;
- `environment.txt` records `dotnet --info`, configured NuGet sources, and the
discovered test-project set;
- `test-results/` contains one TRX per assembly plus any VSTest hang sequence
and dump files;
- `logs/` contains the exact command and complete output for every child
process; and
- `SHA256SUMS.txt` hashes the evidence bundle.
The complete gate runs on Windows because it exercises the full product and
launcher surface. `.github/workflows/headless-portability.yml` remains the
focused Windows/Linux portability and Vulkan evidence workflow; those jobs are
not substitutes for the complete gate.
Environment-dependent tests retain their current skip behavior and are counted
explicitly in the JSON/TRX report. Classifying or replacing those tests belongs
to R3, not to this gate checkpoint.

View file

@ -178,14 +178,26 @@ external systems.
## Post-audit stabilization update — 2026-08-18
R1 has been implemented and fully gated in the current uncommitted working
tree. The launcher child is detached under the supervisor gate and stopped,
R1 has been implemented, fully gated, and preserved at `0a934cf5` on
`codex/release-stabilization`. The launcher child is detached under the supervisor gate and stopped,
unsubscribed, and disposed outside it; a deterministic captured-callback race
test fails against the old mechanism and passes against the fix. Launcher.Core
passes 339/339, and two fresh serialized complete Release runs each finish
inside a 900-second hard bound with 14,748 passes / 77 skips / 0 failures.
This resolves F-009 technically but does not change the baseline audit's
historical count until the coherent R1 checkpoint is reviewed and committed.
The public-release decision remains no-go because F-001, F-002, F-014, and the
remaining high-priority release-governance findings are untouched.
historical count; the checkpoint is committed but not yet merged to `main`.
The subsequent R2 gate checkpoint pins SDK feature band `10.0.300`, adds a
repository-owned bounded Release command and Windows CI workflow that discover
and run all 12 test assemblies, captures TRX/log/environment/hash evidence, and
collects mini dumps before an outer process-tree timeout. Its local evidence
run reports 14,748 executed and passed / 77 skipped / 0 failed in 107.337
seconds. Three additional default-parallel solution runs pass without the
previous Avalonia compositor cleanup failure after shown test windows were
closed on their owning session.
This resolves F-014 technically on the campaign branch and partially addresses
F-010/F-019. The public-release decision remains no-go because F-001, F-002,
the 26 clean-build warnings, unpinned package graph, and the remaining
high-priority release-governance/test-truth findings are untouched.

View file

@ -214,8 +214,8 @@ did not find an actionable exception beyond the documented classes.
test that proves both exit-callback and disposal orders converge. Audit the
ordinary `SystemChildProcess` wrapper for the same inversion.
**Post-baseline resolution checkpoint (2026-08-18, uncommitted):** the working
tree now performs the ownership transfer and all child operations outside
**Post-baseline resolution checkpoint (2026-08-18, `0a934cf5`):** the campaign
branch now performs the ownership transfer and all child operations outside
`_gate`; `OnProcessExited` obtains its optional exit code without holding that
gate. A barrier-controlled regression captures the exit delegate before
unsubscription and makes child disposal wait for that callback. It times out in
@ -223,9 +223,8 @@ five seconds with the old lock shape, passes in milliseconds with the fix, and
passed 25/25 fresh-process repetitions. Launcher.Core passes 339/339. Two
fresh serialized complete-solution runs each finished under a 900-second hard
bound with 14,748 passes / 77 skips / 0 failures in 1:28.822 and 1:30.241.
F-009 remains a baseline finding until the coherent R1 checkpoint is reviewed
and committed; the current working-tree mechanism and required gates are
resolved.
F-009 remains in this historical baseline, but its mechanism and required gates
are resolved in a durable campaign-branch commit that is not yet merged.
## F-010 — Published build/test baseline materially overstates the portable gate
@ -250,6 +249,13 @@ resolved.
Fail on unexpected skips and duplicate discovery IDs; do not hand-edit test
totals into multiple living documents.
**Post-baseline gate checkpoint (2026-08-18, commit pending):** the new
repository gate emits per-assembly TRX plus a JSON summary that distinguishes
14,748 executed/passed cases from 77 reported skips and retains the known
duplicate-discovery warning in the process log. This makes the complete
portable count reproducible, but F-010 remains open until R3 classifies false
passes/skips/diagnostics and the stale public headline claims are replaced.
## F-011 — Mandatory WorldBuilder inventory is an obsolete OpenGL design guide
- **Severity:** P1 high
@ -341,6 +347,18 @@ resolved.
split environment-dependent gates explicitly. Give every job a hang timeout
and collect dumps; make the portable complete gate required before release.
**Post-baseline resolution checkpoint (2026-08-18, commit pending):**
`.github/workflows/release-gate.yml` now runs the repository-owned Release gate
on Windows for pull requests, `main` pushes, and manual dispatch. The script
auto-discovers and verifies all 12 solution test projects, runs each once in a
fresh bounded process, arms VSTest mini-dump collection before the outer
process-tree watchdog, and uploads commands, logs, TRX, environment metadata,
aggregate counts, and hashes even on failure. The exact local workflow command
passed 14,748 executed / 77 skipped / 0 failed in 107.337 seconds; an isolated
watchdog probe returned 124 at 2.107 seconds and left no child. F-014 is
technically resolved on the campaign branch, pending commit/review and actual
GitHub execution after publication.
## F-015 — Headline pass totals include hundreds of unexecuted test contracts
- **Severity:** P1 high
@ -471,6 +489,13 @@ resolved.
that proves a clean checkout. Keep tool-only exceptions explicit rather than
allowing per-project drift.
**Post-baseline partial checkpoint (2026-08-18, commit pending):** `global.json`
pins SDK feature band `10.0.300` with `latestPatch` roll-forward, and all five
CI setup sites now consume that file. `docs/release-gate.md` documents the one
complete command and its evidence. F-019 remains open: common compiler/package
settings are still duplicated, there are no package lock files or locked
restore, and the unsupported tool-project decisions have not been made.
## F-020 — The issue ledger is an unbounded mixed tracker, research log, and archive
- **Severity:** P1 high

View file

@ -120,7 +120,7 @@ callback. The test needs a deterministic race harness after the production
fix; the current timing-dependent version alternates between passing in 48 s
and hanging indefinitely.
**Post-baseline resolution checkpoint (2026-08-18, uncommitted):**
**Post-baseline resolution checkpoint (2026-08-18, `0a934cf5`):**
`DisposeAllowsAnAlreadyCapturedExitCallbackToComplete` is now the deterministic
race harness. Explicit barriers make the fake child capture the exit delegate,
release it from inside disposal, and wait for the callback to return. The old
@ -401,3 +401,28 @@ regression contracts with T-011's output apparatus. When a test graduates into
a stable contract, organize it under the owning component/behavior and retain
the issue ID in a trait or comment; keep investigation programs in a separate,
non-default diagnostic project.
### T-017 — Shown Avalonia test windows escaped their owning UI session
Four `MainWindowViewTests` called `window.Show()` but never closed the window.
One default-parallel solution run then failed during Avalonia headless cleanup:
the runner/compositor touched an object from a thread other than the one that
owned it, even though the Launcher project passed alone. The tests now close
shown windows and pump dispatcher cleanup in `finally` on the owning Avalonia
session. No product code, suite serialization, or retry was added. The focused
class passes 13/13, and three fresh default-parallel complete-solution runs pass
all 12 assemblies in 56.719, 56.321, and 58.506 seconds.
### T-018 — A live stderr assertion could disable the producer it observed
`ReadFileEventuallyContainingAsync` used `File.ReadAllTextAsync` while the
ProcessStartInfo stderr callbacks were still draining. Its read handle did not
share writes. If the final callback overlapped that short read, the capture
sink saw a sharing violation, deliberately latched into its no-throw state, and
the test waited five seconds for a line it had itself caused to be dropped. The
first complete repository-gate run exposed exactly that two-of-three-lines
failure. The helper now opens a live reader with
`FileShare.ReadWrite | FileShare.Delete`, matching the production status
tailer; the expected lines and timeout are unchanged. The test subsequently
passed 25/25 fresh-process runs and Launcher.Core passed 339/339 in the final
gate.