From 7d859ae8a219c5d86d1bf54e0c00131f1abe853b Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 15 May 2026 10:52:29 +0900 Subject: [PATCH] Close Windows release artifact verification gap G009 Stream 8 acceptance requires a Windows release artifact quickstart with checksum evidence, not just source-build docs. Add Windows release asset packaging and make the release-readiness check assert the workflow/docs contract. Constraint: Stream 8 requires release artifact quickstart with checksums and no-credential smoke paths. Rejected: Documenting a future Windows asset without workflow support | would leave acceptance unverifiable. Confidence: high Scope-risk: narrow Tested: python3 .github/scripts/check_release_readiness.py; python3 .github/scripts/check_doc_source_of_truth.py; git diff --check Not-tested: actual GitHub release workflow execution on windows-latest. Co-authored-by: OmX --- .github/scripts/check_release_readiness.py | 34 +++++++++++++++++++ .github/workflows/release.yml | 15 ++++++-- ...9-windows-docs-release-verification-map.md | 2 +- docs/windows-install-release.md | 13 ++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/.github/scripts/check_release_readiness.py b/.github/scripts/check_release_readiness.py index d356abb2..31cb68ff 100644 --- a/.github/scripts/check_release_readiness.py +++ b/.github/scripts/check_release_readiness.py @@ -151,11 +151,45 @@ def validate_command_examples(errors: list[str]) -> None: ) + +def validate_release_artifacts(errors: list[str]) -> None: + workflow = ROOT / ".github" / "workflows" / "release.yml" + release_doc = ROOT / "docs" / "windows-install-release.md" + if not workflow.is_file(): + errors.append("missing release workflow: .github/workflows/release.yml") + return + workflow_text = workflow.read_text(encoding="utf-8") + required_workflow_terms = [ + "windows-latest", + "claw.exe", + "claw-windows-x64.exe", + "sha256sum", + "${{ matrix.artifact_name }}.sha256", + ] + for term in required_workflow_terms: + if term not in workflow_text: + errors.append(f"release workflow missing Windows/checksum term: {term}") + if not release_doc.is_file(): + errors.append("missing Windows release quickstart: docs/windows-install-release.md") + return + release_text = release_doc.read_text(encoding="utf-8") + required_doc_terms = [ + "claw-windows-x64.exe", + "claw-windows-x64.exe.sha256", + "Get-FileHash", + "checksum mismatch", + "target\\release\\claw.exe", + ] + for term in required_doc_terms: + if term not in release_text: + errors.append(f"Windows release quickstart missing term: {term}") + def main() -> int: errors: list[str] = [] validate_policies(errors) validate_markdown_links(errors) validate_command_examples(errors) + validate_release_artifacts(errors) if errors: print("release-readiness check failed:", file=sys.stderr) for error in errors: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682a983c..c1a1d942 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,10 @@ jobs: os: macos-14 bin: claw artifact_name: claw-macos-arm64 + - name: windows-x64 + os: windows-latest + bin: claw.exe + artifact_name: claw-windows-x64.exe defaults: run: working-directory: rust @@ -47,22 +51,27 @@ jobs: - name: Build release binary run: cargo build --release -p rusty-claude-cli - - name: Package artifact + - name: Package artifact and checksum shell: bash run: | mkdir -p dist cp "target/release/${{ matrix.bin }}" "dist/${{ matrix.artifact_name }}" chmod +x "dist/${{ matrix.artifact_name }}" + (cd dist && sha256sum "${{ matrix.artifact_name }}" > "${{ matrix.artifact_name }}.sha256") - name: Upload workflow artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }} - path: rust/dist/${{ matrix.artifact_name }} + path: | + rust/dist/${{ matrix.artifact_name }} + rust/dist/${{ matrix.artifact_name }}.sha256 - name: Upload release asset if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: - files: rust/dist/${{ matrix.artifact_name }} + files: | + rust/dist/${{ matrix.artifact_name }} + rust/dist/${{ matrix.artifact_name }}.sha256 fail_on_unmatched_files: true diff --git a/docs/g009-windows-docs-release-verification-map.md b/docs/g009-windows-docs-release-verification-map.md index 68b7daa6..a132e70e 100644 --- a/docs/g009-windows-docs-release-verification-map.md +++ b/docs/g009-windows-docs-release-verification-map.md @@ -19,7 +19,7 @@ Stream 8 source requirement summary: |---|---|---|---| | PowerShell-first Windows install/run path | `README.md` (`Windows setup`, post-build binary location, PowerShell `.exe` examples); `install.sh` (Unix/WSL installer guard) | `python3 .github/scripts/check_doc_source_of_truth.py`; `cargo run -p rusty-claude-cli -- --help` | Current docs explicitly present Windows as a supported PowerShell path for source builds and `claw.exe`; `install.sh` is Linux/macOS/WSL-oriented, so native PowerShell binary usage and WSL installer usage must stay clearly separated. | | Safe provider switching examples | `USAGE.md` (`Auth`, `Local Models`, `Supported Providers & Models`); `docs/MODEL_COMPATIBILITY.md` | `cargo test -p api providers::`; `cargo test -p rusty-claude-cli --test output_format_contract provider_diagnostics_explain_openai_compatible_capabilities -- --nocapture` | Provider docs cover Anthropic API-key vs bearer-token shape, OpenAI-compatible routing, Ollama/OpenRouter/DashScope examples, and prefix routing to avoid ambient credential misrouting. | -| Release artifact quickstart and staged packaging path | `README.md` (`Quick start`, `Post-build: locate the binary and verify`); `.github/workflows/release.yml` | `cargo build --release -p rusty-claude-cli`; `cargo run -p rusty-claude-cli -- version --output-format json` | Current release workflow packages Linux/macOS binaries, while README remains source-build-first. Release docs should name supported release asset platforms explicitly until Windows binary assets are added by the release/CI lane. | +| Release artifact quickstart and staged packaging path | `README.md` (`Quick start`, `Post-build: locate the binary and verify`); `.github/workflows/release.yml`; `docs/windows-install-release.md` | `cargo build --release -p rusty-claude-cli`; `cargo run -p rusty-claude-cli -- version --output-format json`; `python3 .github/scripts/check_release_readiness.py` | Release workflow packages Linux, macOS, and `claw-windows-x64.exe` assets with `.sha256` checksum files. README remains source-build-first, and the Windows quickstart names the checksum verification path. | | Windows smoke CI without live credentials | `.github/workflows/rust-ci.yml`; CLI local-only surfaces in `rust/crates/rusty-claude-cli/src/main.rs` (`help`, `doctor`, resumed `/config`, `status`) | `cargo run -p rusty-claude-cli -- --help`; `cargo run -p rusty-claude-cli -- doctor --output-format json`; `cargo run -p rusty-claude-cli -- status --output-format json`; `cargo run -p rusty-claude-cli -- config --output-format json` | The smoke target is local-only command execution with isolated config and no real provider credentials. If the Windows CI lane is not present in a branch, this map is the integration checklist for that lane. | | License metadata | `rust/Cargo.toml` (`workspace.package.license = "MIT"`) | `grep -n '^license = "MIT"' rust/Cargo.toml` | Cargo metadata declares MIT. A root `LICENSE` file remains the user-facing policy artifact to add if not already present in the policy lane. | | Contribution/security/support policies | Expected root policy docs: `CONTRIBUTING.md`, `SECURITY.md`, `SUPPORT.md`; existing support links in `README.md` | `test -f CONTRIBUTING.md`; `test -f SECURITY.md`; `test -f SUPPORT.md`; `python3 .github/scripts/check_doc_source_of_truth.py` | These files are policy-lane outputs. This map records the exact release gate so missing files fail visibly instead of being inferred from README links. | diff --git a/docs/windows-install-release.md b/docs/windows-install-release.md index ce6ea606..f90ca970 100644 --- a/docs/windows-install-release.md +++ b/docs/windows-install-release.md @@ -26,16 +26,19 @@ cargo build --workspace --release ### Option B: use a release artifact -Use this when a GitHub release publishes a Windows artifact. The exact asset name may include the version and architecture; prefer the `windows-x86_64` / `pc-windows-msvc` ZIP when available. +Use this when a GitHub release publishes a Windows artifact. The release workflow publishes `claw-windows-x64.exe` plus `claw-windows-x64.exe.sha256`; if a future release wraps the binary in a ZIP, prefer the `windows-x86_64` / `pc-windows-msvc` asset and its matching checksum file. ```powershell -$Version = "vX.Y.Z" -$Asset = "claw-$Version-x86_64-pc-windows-msvc.zip" +$Asset = "claw-windows-x64.exe" $InstallRoot = "$env:LOCALAPPDATA\Programs\claw" New-Item -ItemType Directory -Force $InstallRoot | Out-Null -# Download the asset from the release page, then expand it: -Expand-Archive -Path ".\$Asset" -DestinationPath $InstallRoot -Force +# Download $Asset and $Asset.sha256 from the release page, then verify them: +$Actual = (Get-FileHash ".\$Asset" -Algorithm SHA256).Hash.ToLowerInvariant() +$Expected = (Get-Content ".\$Asset.sha256" | Select-Object -First 1).Split()[0].ToLowerInvariant() +if ($Actual -ne $Expected) { throw "checksum mismatch for $Asset" } + +Copy-Item ".\$Asset" "$InstallRoot\claw.exe" -Force & "$InstallRoot\claw.exe" --help & "$InstallRoot\claw.exe" doctor ```