Repository navigation
Merge pull request #253 from CWNU-Open-Source-Community/codex/accept-… #650
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| classify: | |
| name: Classify pull request changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| docs_only: ${{ steps.changes.outputs.docs_only }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Test CI helpers | |
| run: python -B -m unittest discover -s scripts/ci -p 'test_*.py' -v | |
| - name: Classify the exact change set | |
| id: changes | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| run: >- | |
| python -B scripts/ci/classify_changes.py | |
| --event "$EVENT_NAME" | |
| --repo "$GITHUB_WORKSPACE" | |
| --base "$BASE_SHA" | |
| --head "$HEAD_SHA" | |
| >> "$GITHUB_OUTPUT" | |
| go-checks: | |
| name: Go checks | |
| needs: classify | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| # Protocol and Surface history are compared with the exact PR/push | |
| # baseline. The first Surface registry PR is its explicit bootstrap. | |
| fetch-depth: 0 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Report Go toolchain | |
| run: go version | |
| - run: go mod verify | |
| - name: Verify Go module graph is tidy | |
| run: go mod tidy -diff | |
| - name: Verify classified protocol registry and reader history | |
| env: | |
| PROTOCOL_REGISTRY_BASELINE: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| run: >- | |
| go run ./cmd/protocolregistry -check | |
| -baseline "$PROTOCOL_REGISTRY_BASELINE" | |
| - name: Materialize reviewed base Surface registry | |
| shell: bash | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| run: | | |
| if [[ ! "$BASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "GitHub base commit identity is invalid" | |
| exit 1 | |
| fi | |
| registry_path="docs/convergence/surface-registry.json" | |
| if git cat-file -e "${BASE_SHA}:${registry_path}" 2>/dev/null; then | |
| base_registry="$RUNNER_TEMP/surface-registry-base.json" | |
| git show "${BASE_SHA}:${registry_path}" > "$base_registry" | |
| echo "SURFACE_REGISTRY_BASE=$base_registry" >> "$GITHUB_ENV" | |
| else | |
| echo "Base Surface registry is absent; treating this PR as the registry bootstrap." | |
| fi | |
| - name: Verify Surface tier registry and generated inventory | |
| run: go run ./cmd/surfacecheck | |
| - name: Verify documentation release contracts | |
| if: ${{ github.event_name == 'pull_request' && needs.classify.result == 'success' && needs.classify.outputs.docs_only == 'true' }} | |
| run: go test -count=1 ./internal/releasegate ./internal/producte2e | |
| - name: Run complete Go suite except sharded Store package | |
| if: ${{ github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true' }} | |
| # The migration-heavy Store package can exceed 30 minutes on a shared | |
| # Linux runner after a forward schema is added. Shared-runner variance | |
| # has pushed the Store package past 45 minutes. It is split below by | |
| # compiled test name while this job retains every other package. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| module="$(go list -m)" | |
| store_package="$module/internal/store" | |
| package_file="$RUNNER_TEMP/go-packages.txt" | |
| go list ./... > "$package_file" | |
| mapfile -t packages < "$package_file" | |
| filtered=() | |
| store_seen=false | |
| for package in "${packages[@]}"; do | |
| if [[ "$package" == "$store_package" ]]; then | |
| store_seen=true | |
| else | |
| filtered+=("$package") | |
| fi | |
| done | |
| if [[ "$store_seen" != true || "${#filtered[@]}" -eq 0 ]]; then | |
| echo "Exact internal/store package exclusion failed" >&2 | |
| exit 1 | |
| fi | |
| go test -timeout 60m -count=1 "${filtered[@]}" | |
| - name: Race-check Workspace Access permission and authority fencing | |
| if: ${{ github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true' }} | |
| run: >- | |
| go test -race -timeout 15m -count=1 | |
| ./internal/domain ./internal/executionauth ./internal/application | |
| ./internal/store ./internal/httpapi | |
| -run 'WorkspaceAccess|RunExecutionPermission|ExecutionPermission|PermissionDrift|SchemaV126|AgentCodeExecutorCreatesReviewedFileAndFailsClosedOnCASConflict|CommandOperationApprovalRealNativeThreeModes' | |
| - if: ${{ github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true' }} | |
| run: go vet ./... | |
| - name: Install pinned govulncheck | |
| if: ${{ github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true' }} | |
| env: | |
| GOBIN: ${{ runner.temp }} | |
| run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 | |
| - name: Scan for reachable Go vulnerabilities | |
| if: ${{ github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true' }} | |
| env: | |
| GOVULNCHECK: ${{ runner.temp }}/govulncheck | |
| run: | | |
| "$GOVULNCHECK" -version | |
| "$GOVULNCHECK" ./... | |
| store-tests: | |
| name: Store package tests (shard ${{ matrix.shard }}) | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [0, 1, 2, 3, 4, 5, 6, 7] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Run exact Store shard | |
| run: >- | |
| python -B scripts/ci/run_store_shard.py | |
| --index ${{ matrix.shard }} | |
| --count 8 | |
| --output-dir "$RUNNER_TEMP/store-shard-${{ matrix.shard }}" | |
| - name: Upload Store shard evidence | |
| if: ${{ always() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: store-shard-${{ matrix.shard }} | |
| path: ${{ runner.temp }}/store-shard-${{ matrix.shard }}/ | |
| if-no-files-found: error | |
| retention-days: 5 | |
| go: | |
| name: Go control plane | |
| needs: | |
| - classify | |
| - go-checks | |
| - store-tests | |
| if: ${{ always() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Require every applicable Go gate | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| DOCS_ONLY: ${{ needs.classify.outputs.docs_only }} | |
| CLASSIFY_RESULT: ${{ needs.classify.result }} | |
| GO_CHECKS_RESULT: ${{ needs.go-checks.result }} | |
| STORE_TESTS_RESULT: ${{ needs.store-tests.result }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$CLASSIFY_RESULT" != success || "$GO_CHECKS_RESULT" != success ]]; then | |
| echo "Change classification or Go checks did not succeed" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$EVENT_NAME" == pull_request && "$DOCS_ONLY" == true ]]; then | |
| if [[ "$STORE_TESTS_RESULT" != skipped ]]; then | |
| echo "Documentation-only Store shards were not skipped as required" >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$STORE_TESTS_RESULT" != success ]]; then | |
| echo "One or more Store shards did not succeed" >&2 | |
| exit 1 | |
| fi | |
| echo "All applicable Go gates succeeded" | |
| code-intel-lsp: | |
| name: Real LSP (${{ matrix.os }}) | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24 | |
| - name: Install reviewed real language servers | |
| shell: pwsh | |
| run: | | |
| $goBin = Join-Path $env:RUNNER_TEMP "prayu-gopls" | |
| $tsRoot = Join-Path $env:RUNNER_TEMP "prayu-typescript-lsp" | |
| New-Item -ItemType Directory -Path $goBin -Force | Out-Null | |
| $env:GOBIN = $goBin | |
| go install golang.org/x/tools/gopls@v0.21.1 | |
| npm install --prefix $tsRoot --ignore-scripts --no-audit --no-fund ` | |
| --package-lock=false typescript-language-server@6.0.0 typescript@5.9.3 | |
| $suffix = if ($IsWindows) { ".exe" } else { "" } | |
| $gopls = Join-Path $goBin "gopls$suffix" | |
| $node = (Get-Command node).Source | |
| $typescriptServer = Join-Path $tsRoot ` | |
| "node_modules/typescript-language-server/lib/cli.mjs" | |
| foreach ($path in @($gopls, $node, $typescriptServer)) { | |
| if (-not [System.IO.Path]::IsPathFullyQualified($path) -or | |
| -not (Test-Path -LiteralPath $path -PathType Leaf)) { | |
| throw "reviewed real LSP dependency is unavailable: $path" | |
| } | |
| } | |
| "CYBERAGENT_GOPLS_SMOKE_BINARY=$gopls" | Out-File ` | |
| -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| "CYBERAGENT_TYPESCRIPT_LSP_SMOKE_NODE=$node" | Out-File ` | |
| -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| "CYBERAGENT_TYPESCRIPT_LSP_SMOKE_CLI=$typescriptServer" | Out-File ` | |
| -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Exercise reviewed gopls and TypeScript LSP tool sets | |
| run: >- | |
| go test -v -count=1 -timeout 5m ./internal/codeintel | |
| -run '^TestReal(Gopls|TypeScriptLanguageServer)ReadOnlySemanticToolSmoke$' | |
| - name: Run code-intel race and fault suite | |
| if: runner.os == 'Linux' | |
| run: go test -race -count=1 -timeout 5m ./internal/codeintel | |
| web: | |
| name: TypeScript console | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: web | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci | |
| - run: npm run check:api | |
| - run: npm test | |
| - run: npm run build | |
| - name: Audit dependencies | |
| run: | | |
| for attempt in 1 2 3; do | |
| if npm audit --audit-level=high; then | |
| exit 0 | |
| fi | |
| if [ "$attempt" -lt 3 ]; then | |
| sleep $((attempt * 5)) | |
| fi | |
| done | |
| exit 1 | |
| rust: | |
| name: Rust analyzer fixtures | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: analyzers | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Report Go toolchain | |
| run: go version | |
| - name: Install pinned Rust toolchain | |
| run: rustup toolchain install 1.97.1 --profile minimal --component rustfmt --component clippy | |
| - run: cargo fmt --all -- --check | |
| - run: cargo test --locked | |
| - run: cargo clippy --locked --all-targets -- -D warnings | |
| - name: Audit locked Rust dependencies | |
| env: | |
| CARGO_AUDIT_VERSION: 0.22.2 | |
| CARGO_AUDIT_SHA256: ab28a1bdb54db4d5d8ad5981cf1f959410370b3d28250dbd35f6a44248620e39 | |
| run: | | |
| archive="$RUNNER_TEMP/cargo-audit.tgz" | |
| audit_root="$RUNNER_TEMP/cargo-audit" | |
| advisory_db="$RUNNER_TEMP/rustsec-advisory-db" | |
| curl --fail --location --retry 3 --retry-all-errors \ | |
| --output "$archive" \ | |
| "https://github.com/RustSec/rustsec/releases/download/cargo-audit/v${CARGO_AUDIT_VERSION}/cargo-audit-x86_64-unknown-linux-gnu-v${CARGO_AUDIT_VERSION}.tgz" | |
| echo "${CARGO_AUDIT_SHA256} ${archive}" | sha256sum --check | |
| mkdir -p "$audit_root" | |
| tar --extract --gzip --file "$archive" --directory "$audit_root" | |
| git clone --depth 1 https://github.com/RustSec/advisory-db.git "$advisory_db" | |
| git -C "$advisory_db" log -1 --format='RustSec advisory DB: %H (%cI)' | |
| "$audit_root/cargo-audit-x86_64-unknown-linux-gnu-v${CARGO_AUDIT_VERSION}/cargo-audit" \ | |
| audit --no-fetch --no-yanked --db "$advisory_db" --file Cargo.lock | |
| - name: Build deterministic Rust fixture | |
| run: cargo build --locked --bin cyberagent-analyzer-fixture | |
| - name: Build embedded WASI analyzer fixture | |
| working-directory: . | |
| run: ./scripts/build-embedded-wasi.sh | |
| - name: Verify rebuilt WASI matches the embedded product module | |
| id: verify-wasi | |
| working-directory: . | |
| env: | |
| REBUILT_WASI_FIXTURE: ${{ github.workspace }}/analyzers/target/wasm32-wasip1/release/cyberagent-analyzer-fixture.wasm | |
| EMBEDDED_WASI_FIXTURE: ${{ github.workspace }}/internal/analyzer/embedded/cyberagent-analyzer-fixture.wasm | |
| run: | | |
| if ! cmp --silent "$REBUILT_WASI_FIXTURE" "$EMBEDDED_WASI_FIXTURE"; then | |
| echo "The Rust fixture and the product-embedded WASI module differ." | |
| sha256sum "$REBUILT_WASI_FIXTURE" "$EMBEDDED_WASI_FIXTURE" | |
| echo "Run ./scripts/build-embedded-wasi.sh and replace internal/analyzer/embedded/cyberagent-analyzer-fixture.wasm." | |
| exit 1 | |
| fi | |
| sha256sum "$EMBEDDED_WASI_FIXTURE" | |
| - name: Preserve a mismatched rebuilt WASI module | |
| if: failure() && steps.verify-wasi.outcome == 'failure' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: rebuilt-wasi-fixture | |
| path: analyzers/target/wasm32-wasip1/release/cyberagent-analyzer-fixture.wasm | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Verify embedded WASI import and memory boundaries | |
| working-directory: . | |
| env: | |
| CYBERAGENT_WASI_FIXTURE: ${{ github.workspace }}/analyzers/target/wasm32-wasip1/release/cyberagent-analyzer-fixture.wasm | |
| run: go test -count=1 ./internal/analyzer -run '^TestAnalyzerEmbeddedWASIProductionFixtureAssessment$' | |
| - name: Verify Linux analyzer conformance boundaries | |
| working-directory: . | |
| env: | |
| CYBERAGENT_ANALYZER_CONFORMANCE_BINARY: ${{ github.workspace }}/analyzers/target/debug/cyberagent-analyzer-fixture | |
| run: go test -count=1 ./internal/analyzer -run '^Test(SubprocessConformance|AnalyzerResultStaging|Analyzer(ImmutableHandleHandoff|DedicatedLowPrivilegeIdentity|ReadOnlyFilesystemPrivateStaging)Conformance)' | |
| desktop-macos: | |
| name: macOS Desktop shell | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Report Go toolchain | |
| run: go version | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - name: Build embedded renderer | |
| working-directory: web | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Verify isolated macOS Keychain persistence | |
| env: | |
| CGO_ENABLED: '1' | |
| TRAVERSE_TEST_KEYCHAIN: '1' | |
| run: go test -v -tags keychainintegration -count=1 -timeout=90s ./internal/credential | |
| - name: Verify POSIX command runtime transport | |
| run: go test -v -count=1 ./internal/runner -run '^TestCommandRuntimePlatform(ShellSmoke|CancelReapsOwnedProcessTree)$' | |
| - name: Build and verify downloadable macOS preview | |
| run: bash scripts/release-desktop-darwin.sh "v1.0.0-ci.$GITHUB_RUN_ID" "$GITHUB_SHA" "$(go env GOARCH)" | |
| - name: Upload downloadable macOS preview | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: TraverseBoard-macos-preview-arm64 | |
| path: build/macos-public/* | |
| if-no-files-found: error | |
| retention-days: 30 | |
| desktop: | |
| name: Windows Desktop shell | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: windows-latest | |
| env: | |
| TRAVERSE_BOARD_LOCAL_SANDBOX_TEST_HOST_PREP: '1' | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| id: windows-go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Report Go toolchain | |
| run: go version | |
| - name: Prepare official Go at the fixed native installation path | |
| shell: pwsh | |
| env: | |
| TRAVERSE_CI_GO_VERSION: ${{ steps.windows-go.outputs.go-version }} | |
| run: | | |
| ./scripts/tests/prepare-windows-fixed-go.tests.ps1 | |
| ./scripts/prepare-windows-fixed-go.ps1 -ExpectedGoVersion $env:TRAVERSE_CI_GO_VERSION | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install pinned Rust toolchain | |
| run: rustup toolchain install 1.97.1 --profile minimal | |
| - name: Build deterministic Rust fixture | |
| working-directory: analyzers | |
| run: cargo +1.97.1 build --locked --bin cyberagent-analyzer-fixture | |
| - name: Build embedded renderer | |
| working-directory: web | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Verify Windows Local Sandbox isolation and product gates | |
| shell: pwsh | |
| run: | | |
| go test -v -count=1 -timeout 10m ./internal/app -run '^TestWindowsAPIServe' | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| $cliTests = @( | |
| 'TestRunCommandExecuteIsConfirmedAuditedAndExactlyOnce' | |
| 'TestCLIExecutionEntrypointsWireOrdinaryAgentBrowser' | |
| 'TestCLIWakeConsumeWiresOrdinaryAgentBrowser' | |
| 'TestCLIApprovalExecutionHandoffWiresOrdinaryAgentBrowser' | |
| 'TestCLIExecutionRuntimeClosesAgentBrowserWithoutGrantingPersistedFullAccess' | |
| 'TestCLIApprovalContinuationRequiresInvocationFullConfirmation' | |
| 'TestCLIFileReviewKeepsExactDecisionSeparateFromContinuationFull' | |
| 'TestCLIApprovalCompletedContinuationReplaysWithoutModelOrTool' | |
| 'TestCLIApprovalDenyContinuesOnceAndReplaysAcrossApps' | |
| 'TestCLIApprovalDoesNotWakePausedOrCancelledRun' | |
| ) | |
| go test -v -count=1 -timeout 10m ./internal/app -run ('^(' + ($cliTests -join '|') + ')$') | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| # Preserve the operator-first launch order and retain both results. | |
| go test -v -count=1 -timeout 10m ./internal/application -run '^TestOperatorFixedCommandUsesSharedJobLedger$' | |
| $fixedOperatorExitCode = $LASTEXITCODE | |
| go test -v -count=1 -timeout 10m ./internal/runner -run '^TestWindowsFixedCommandRuntimeUsesRestrictedNativeProcess$' | |
| if ($fixedOperatorExitCode -ne 0) { exit $fixedOperatorExitCode } | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| go test -v -count=1 -timeout 10m ./internal/sandbox -run '^TestWindowsLocalSandbox' | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| # Local command manifests bind Windows drive roots and LPAC policy. | |
| # Run their launch freshness/capability tests on the actual platform. | |
| go test -v -count=1 -timeout 10m ./internal/application -run '^TestLocalCommand' | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| - name: Verify cross-platform Runner receipt vectors | |
| run: go test -count=1 ./internal/runner -run 'TestEvidenceSetReceipt(Golden|Compatibility).*Vectors' | |
| - name: Verify command operation authority and cross-turn ownership | |
| run: >- | |
| go test -v -count=1 -timeout 10m ./internal/application ./internal/workspace | |
| -run '^(TestCommandOperationApproval.*|TestCommandOperationRechecksAfterPrepareAndAtNativeLaunch|TestCommandOperationInitialInputOutlivesRequestButRechecksAuthority|TestCommandRuntimeForegroundCancellationReapsProcessTree|TestCommandRuntimeUIEvidenceCleanupReapsExactJobAfterLeaseRelease|TestThreadBackgroundCommandWaitRetainsJobUntilExplicitPause|TestWorkspaceInit.*|TestWorkspaceImport.*DirectoryAlias)$' | |
| - name: Verify Windows command runtime transports | |
| run: go test -v -count=1 ./internal/runner -run '^TestCommandRuntime(Platform(ShellSmoke|CancelReapsOwnedProcessTree)|WindowsPowerShell(5PowerShell7AndGitBashSmoke|5BindsWorkspaceProfileBeforeLaunch|5ProfileWatchdogReapsProcess|7KeepsExistingProfileEnvironment))$' | |
| - name: Verify Windows analyzer conformance boundaries | |
| shell: pwsh | |
| run: | | |
| $env:CYBERAGENT_ANALYZER_CONFORMANCE_BINARY = Join-Path $env:GITHUB_WORKSPACE "analyzers\target\debug\cyberagent-analyzer-fixture.exe" | |
| go test -v -count=1 ./internal/analyzer -run '^Test(SubprocessConformance|AnalyzerResultStaging|AnalyzerSandboxEnforcementConformance|Analyzer(ImmutableHandleHandoff|DedicatedLowPrivilegeIdentity|ReadOnlyFilesystemPrivateStaging)Conformance)' | |
| - name: Build and verify portable Desktop shell | |
| shell: pwsh | |
| run: ./scripts/build-desktop.ps1 -SkipFrontend -VerifyReproducible | |
| ui-evidence-windows: | |
| name: Real Edge UI evidence (standard user, Windows 2022) | |
| needs: classify | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || needs.classify.result != 'success' || needs.classify.outputs.docs_only != 'true') }} | |
| runs-on: windows-2022 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - name: Set up current Go 1.25 patch | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: '1.25.x' | |
| check-latest: true | |
| cache: true | |
| - name: Verify Local Sandbox on the Windows 2022 compatibility host | |
| env: | |
| TRAVERSE_BOARD_LOCAL_SANDBOX_TEST_HOST_PREP: '1' | |
| run: go test -v -count=1 -timeout 10m ./internal/sandbox -run '^TestWindowsLocalSandbox' | |
| - name: Verify real Edge UI evidence matrix and regression detection | |
| shell: pwsh | |
| run: | | |
| $artifactDirectory = Join-Path $env:RUNNER_TEMP "ui-evidence-smoke" | |
| New-Item -ItemType Directory -Path $artifactDirectory -Force | Out-Null | |
| if ($env:GITHUB_RUN_ID -notmatch '^\d+$' -or $env:GITHUB_RUN_ATTEMPT -notmatch '^\d+$') { | |
| throw "GitHub run identity is unavailable" | |
| } | |
| # Hosted Windows RUNNER_TEMP is reparse-backed, and runneradmin's | |
| # LOCALAPPDATA is not traversable by the disposable standard user. | |
| # Use one exact child of the direct system volume root, then grant | |
| # that child (never the root) only to the temporary SID below. | |
| if ([string]::IsNullOrWhiteSpace($env:SystemRoot) -or | |
| -not [System.IO.Path]::IsPathFullyQualified($env:SystemRoot)) { | |
| throw "Windows system root is unavailable" | |
| } | |
| $directTempParent = [System.IO.Path]::GetPathRoot( | |
| [System.IO.Path]::GetFullPath($env:SystemRoot)) | |
| $directTempParentItem = Get-Item -LiteralPath $directTempParent -Force | |
| if (-not $directTempParentItem.PSIsContainer -or | |
| ($directTempParentItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { | |
| throw "Windows system volume root is indirect" | |
| } | |
| $directTempName = "cyberagent-ui-evidence-$($env:GITHUB_RUN_ID)-$($env:GITHUB_RUN_ATTEMPT)" | |
| $directTempRoot = Join-Path $directTempParent $directTempName | |
| $resolvedTempParent = [System.IO.Path]::GetFullPath((Split-Path -Parent $directTempRoot)) | |
| if (-not [string]::Equals( | |
| $resolvedTempParent.TrimEnd('\'), | |
| $directTempParent.TrimEnd('\'), | |
| [System.StringComparison]::OrdinalIgnoreCase)) { | |
| throw "UI evidence direct temp root escaped the system volume root" | |
| } | |
| if (Test-Path -LiteralPath $directTempRoot) { | |
| throw "UI evidence direct temp root already exists" | |
| } | |
| $directTempItem = New-Item -ItemType Directory -Path $directTempRoot | |
| if (($directTempItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { | |
| throw "UI evidence direct temp root is indirect" | |
| } | |
| $directArtifactDirectory = Join-Path $directTempRoot "artifacts" | |
| $testBinary = Join-Path $directTempRoot "browserruntime.test.exe" | |
| $userHome = Join-Path $directTempRoot "home" | |
| $userLocalAppData = Join-Path $userHome "AppData\Local" | |
| $userRoamingAppData = Join-Path $userHome "AppData\Roaming" | |
| foreach ($directory in @($directArtifactDirectory, $userLocalAppData, $userRoamingAppData)) { | |
| New-Item -ItemType Directory -Path $directory -Force | Out-Null | |
| } | |
| $runSuffix = $env:GITHUB_RUN_ID.Substring( | |
| [Math]::Max(0, $env:GITHUB_RUN_ID.Length - 8)) | |
| $standardUserName = "cyberui$runSuffix$($env:GITHUB_RUN_ATTEMPT)" | |
| if ($standardUserName -notmatch '^cyberui\d{1,12}$' -or | |
| $standardUserName.Length -gt 20) { | |
| throw "derived UI evidence standard-user name is invalid" | |
| } | |
| if (Get-LocalUser -Name $standardUserName -ErrorAction SilentlyContinue) { | |
| throw "UI evidence standard user already exists" | |
| } | |
| $standardUserCreated = $false | |
| $workspaceGrantAdded = $false | |
| $standardUserSID = $null | |
| $process = $null | |
| $securePassword = $null | |
| $cleanupFailure = $null | |
| try { | |
| go test -c -o $testBinary ./internal/browserruntime | |
| if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $testBinary)) { | |
| throw "compile real Edge UI evidence test binary failed" | |
| } | |
| $plainPassword = "Aa1!$([guid]::NewGuid().ToString('N'))zZ9!" | |
| $securePassword = ConvertTo-SecureString $plainPassword -AsPlainText -Force | |
| New-LocalUser -Name $standardUserName -Password $securePassword ` | |
| -AccountNeverExpires -PasswordNeverExpires -UserMayNotChangePassword | Out-Null | |
| $standardUserCreated = $true | |
| $usersGroup = Get-LocalGroup -SID "S-1-5-32-545" | |
| Add-LocalGroupMember -Group $usersGroup -Member $standardUserName | |
| $standardUserSID = (Get-LocalUser -Name $standardUserName).SID.Value | |
| if ($standardUserSID -notmatch '^S-1-5-21-(\d+-){3}\d+$') { | |
| throw "UI evidence standard-user SID is invalid" | |
| } | |
| & icacls.exe $env:GITHUB_WORKSPACE /grant:r ` | |
| ("*{0}:(OI)(CI)RX" -f $standardUserSID) /Q | Out-Host | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "grant standard-user workspace read access failed" | |
| } | |
| $workspaceGrantAdded = $true | |
| & icacls.exe $directTempRoot /grant:r ` | |
| ("*{0}:(OI)(CI)M" -f $standardUserSID) /Q | Out-Host | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "grant standard-user temporary-root access failed" | |
| } | |
| $start = [System.Diagnostics.ProcessStartInfo]::new() | |
| $start.FileName = $testBinary | |
| $start.WorkingDirectory = $env:GITHUB_WORKSPACE | |
| $start.UseShellExecute = $false | |
| $start.CreateNoWindow = $true | |
| $start.RedirectStandardOutput = $true | |
| $start.RedirectStandardError = $true | |
| $start.Domain = $env:COMPUTERNAME | |
| $start.UserName = $standardUserName | |
| $start.Password = $securePassword | |
| # Edge requires the disposable account's HKCU hive during browser | |
| # initialization. The exact profile is removed by SID below. | |
| $start.LoadUserProfile = $true | |
| $start.ArgumentList.Add("-test.v") | |
| $start.ArgumentList.Add("-test.timeout=3m") | |
| $start.ArgumentList.Add( | |
| "-test.run=^TestInstalledEdgeUIEvidenceHeadlessMatrixAndRegression$") | |
| $start.Environment.Clear() | |
| $processEnvironment = [ordered]@{ | |
| APPDATA = $userRoamingAppData | |
| CYBERAGENT_UI_EVIDENCE_ARTIFACT_DIR = $directArtifactDirectory | |
| CYBERAGENT_UI_EVIDENCE_SMOKE = "1" | |
| GITHUB_ACTIONS = "true" | |
| GIT_CONFIG_COUNT = "1" | |
| GIT_CONFIG_GLOBAL = "NUL" | |
| GIT_CONFIG_KEY_0 = "safe.directory" | |
| GIT_CONFIG_NOSYSTEM = "1" | |
| GIT_CONFIG_VALUE_0 = $env:GITHUB_WORKSPACE | |
| GIT_OPTIONAL_LOCKS = "0" | |
| GIT_TERMINAL_PROMPT = "0" | |
| HOME = $userHome | |
| LOCALAPPDATA = $userLocalAppData | |
| PATH = "C:\Program Files\Git\cmd;$env:SystemRoot\System32;$env:SystemRoot" | |
| PATHEXT = ".COM;.EXE;.BAT;.CMD" | |
| SystemRoot = $env:SystemRoot | |
| TEMP = $directTempRoot | |
| TMP = $directTempRoot | |
| USERPROFILE = $userHome | |
| WINDIR = $env:WINDIR | |
| } | |
| foreach ($entry in $processEnvironment.GetEnumerator()) { | |
| $start.Environment[$entry.Key] = $entry.Value | |
| } | |
| $process = [System.Diagnostics.Process]::new() | |
| $process.StartInfo = $start | |
| if (-not $process.Start()) { | |
| throw "start real Edge UI evidence as a standard user failed" | |
| } | |
| $plainPassword = $null | |
| $stdout = $process.StandardOutput.ReadToEndAsync() | |
| $stderr = $process.StandardError.ReadToEndAsync() | |
| if (-not $process.WaitForExit(240000)) { | |
| $process.Kill($true) | |
| $process.WaitForExit() | |
| throw "real Edge UI evidence standard-user process timed out" | |
| } | |
| $stdout.Result | Write-Host | |
| $stderr.Result | Write-Host | |
| if (Test-Path -LiteralPath $directArtifactDirectory) { | |
| Get-ChildItem -LiteralPath $directArtifactDirectory -File | ForEach-Object { | |
| Copy-Item -LiteralPath $_.FullName -Destination $artifactDirectory -Force | |
| } | |
| } | |
| if ($process.ExitCode -ne 0) { | |
| throw "real Edge UI evidence verification failed with exit code $($process.ExitCode)" | |
| } | |
| if (-not (Test-Path -LiteralPath (Join-Path $artifactDirectory "receipt.json"))) { | |
| throw "real Edge UI evidence receipt was not produced" | |
| } | |
| } | |
| finally { | |
| if ($null -ne $process) { | |
| $process.Dispose() | |
| } | |
| if ($null -ne $securePassword) { | |
| $securePassword.Dispose() | |
| } | |
| if ($workspaceGrantAdded -and $null -ne $standardUserSID) { | |
| & icacls.exe $env:GITHUB_WORKSPACE /remove:g ` | |
| ("*{0}" -f $standardUserSID) /Q | Out-Host | |
| if ($LASTEXITCODE -ne 0) { | |
| $cleanupFailure = "remove standard-user workspace grant failed" | |
| } | |
| } | |
| if ($standardUserCreated) { | |
| $profileDeadline = (Get-Date).AddSeconds(5) | |
| do { | |
| $loadedProfile = Get-CimInstance -ClassName Win32_UserProfile | | |
| Where-Object { $_.SID -eq $standardUserSID } | |
| if ($null -eq $loadedProfile -or -not $loadedProfile.Loaded) { | |
| break | |
| } | |
| Start-Sleep -Milliseconds 250 | |
| } while ((Get-Date) -lt $profileDeadline) | |
| if ($null -ne $loadedProfile) { | |
| if ($loadedProfile.Special -or $loadedProfile.Loaded) { | |
| $cleanupFailure = "UI evidence standard-user profile is not removable" | |
| } | |
| else { | |
| $loadedProfile | Remove-CimInstance | |
| } | |
| } | |
| Remove-LocalUser -Name $standardUserName | |
| } | |
| if (Test-Path -LiteralPath $directTempRoot) { | |
| Remove-Item -LiteralPath $directTempRoot -Recurse -Force | |
| } | |
| if (Test-Path -LiteralPath $directTempRoot) { | |
| throw "UI evidence direct temp root was not cleaned" | |
| } | |
| if ($null -ne $cleanupFailure) { | |
| throw $cleanupFailure | |
| } | |
| } | |
| - name: Upload real Edge UI evidence receipt | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ui-evidence-edge-smoke | |
| path: ${{ runner.temp }}/ui-evidence-smoke | |
| if-no-files-found: warn | |
| retention-days: 5 |