Upstream Build App #63
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: Upstream Build App | |
| on: | |
| schedule: | |
| - cron: '30 * * * *' | |
| pull_request: | |
| paths: | |
| - install.sh | |
| - Makefile | |
| - scripts/patch-linux-window-ui.js | |
| - scripts/patch-linux-window-ui.test.js | |
| - scripts/patches/** | |
| - scripts/ci/validate-patch-report.js | |
| - scripts/ci/upstream-dmg-*.js | |
| - scripts/validate-upstream-dmg.js | |
| - scripts/lib/candidate-install.sh | |
| - scripts/lib/bundled-plugins.sh | |
| - scripts/lib/browser-client-node-repl-runtime.test.js | |
| - scripts/lib/patch-browser-client-iab-socket-scope.js | |
| - scripts/lib/patch-validation.js | |
| - scripts/lib/upstream-dmg-acceptance.js | |
| - scripts/lib/upstream-dmg-release-profile.js | |
| - scripts/lib/linux-features.js | |
| - computer-use-linux/src/bin/codex-chrome-extension-host.rs | |
| - linux-features/** | |
| - .github/workflows/upstream-build-app.yml | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - install.sh | |
| - Makefile | |
| - scripts/patch-linux-window-ui.js | |
| - scripts/patch-linux-window-ui.test.js | |
| - scripts/patches/** | |
| - scripts/ci/validate-patch-report.js | |
| - scripts/ci/upstream-dmg-*.js | |
| - scripts/validate-upstream-dmg.js | |
| - scripts/lib/candidate-install.sh | |
| - scripts/lib/bundled-plugins.sh | |
| - scripts/lib/browser-client-node-repl-runtime.test.js | |
| - scripts/lib/patch-browser-client-iab-socket-scope.js | |
| - scripts/lib/patch-validation.js | |
| - scripts/lib/upstream-dmg-acceptance.js | |
| - scripts/lib/upstream-dmg-release-profile.js | |
| - scripts/lib/linux-features.js | |
| - computer-use-linux/src/bin/codex-chrome-extension-host.rs | |
| - linux-features/** | |
| - .github/workflows/upstream-build-app.yml | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: upstream-dmg-acceptance-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| UPSTREAM_DMG_URL: https://persistent.oaistatic.com/codex-app-prod/ChatGPT.dmg | |
| UPSTREAM_DMG_PATH: /tmp/codex-upstream-ci/Codex.dmg | |
| DMG_CACHE_SCHEMA_VERSION: v1 | |
| jobs: | |
| build-app-upstream-dmg: | |
| name: Build App Against Upstream DMG | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24 | |
| - name: Install build dependencies | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y p7zip-full make g++ python3 curl unzip | |
| - name: Capture upstream DMG metadata | |
| id: upstream-metadata | |
| run: | | |
| set -euo pipefail | |
| headers_file="$(mktemp)" | |
| trap 'rm -f "$headers_file"' EXIT | |
| curl -fsSLI "$UPSTREAM_DMG_URL" > "$headers_file" | |
| # Match header names case-insensitively without gawk's IGNORECASE, | |
| # which is a no-op under mawk (the default awk on ubuntu runners). | |
| last_modified="$(awk 'tolower($0) ~ /^last-modified:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); print; exit}' "$headers_file")" | |
| etag="$(awk 'tolower($0) ~ /^etag:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); gsub(/"/,""); print; exit}' "$headers_file")" | |
| content_length="$(awk 'tolower($0) ~ /^content-length:/ {sub(/\r$/,""); sub(/^[^:]+: /,""); print; exit}' "$headers_file")" | |
| if [ -z "$last_modified" ]; then | |
| last_modified="unknown" | |
| fi | |
| if [ -z "$etag" ]; then | |
| etag="no-etag" | |
| fi | |
| if [ -z "$content_length" ]; then | |
| content_length="unknown" | |
| fi | |
| cache_segment="$(printf '%s|%s|%s\n' "$last_modified" "$etag" "$content_length" | sha256sum | cut -d' ' -f1)" | |
| { | |
| echo "last_modified=$last_modified" | |
| echo "etag=$etag" | |
| echo "content_length=$content_length" | |
| echo "cache_segment=$cache_segment" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Restore cached upstream DMG | |
| id: dmg-cache | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: /tmp/codex-upstream-ci/Codex.dmg | |
| key: upstream-dmg-${{ env.DMG_CACHE_SCHEMA_VERSION }}-${{ steps.upstream-metadata.outputs.cache_segment }} | |
| - name: Download upstream DMG | |
| if: steps.dmg-cache.outputs.cache-hit != 'true' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$(dirname "$UPSTREAM_DMG_PATH")" | |
| curl -fL --retry 3 -o "$UPSTREAM_DMG_PATH" "$UPSTREAM_DMG_URL" | |
| - name: Record local DMG fingerprint | |
| id: local-dmg | |
| run: | | |
| set -euo pipefail | |
| test -s "$UPSTREAM_DMG_PATH" | |
| dmg_sha256="$(sha256sum "$UPSTREAM_DMG_PATH" | cut -d' ' -f1)" | |
| dmg_size_bytes="$(stat -c '%s' "$UPSTREAM_DMG_PATH")" | |
| tested_at_utc="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" | |
| { | |
| echo "sha256=$dmg_sha256" | |
| echo "size_bytes=$dmg_size_bytes" | |
| echo "tested_at_utc=$tested_at_utc" | |
| } >> "$GITHUB_OUTPUT" | |
| cat > upstream-dmg-metadata.json <<JSON | |
| { | |
| "url": "$UPSTREAM_DMG_URL", | |
| "path": "$UPSTREAM_DMG_PATH", | |
| "last_modified": "${{ steps.upstream-metadata.outputs.last_modified }}", | |
| "etag": "${{ steps.upstream-metadata.outputs.etag }}", | |
| "content_length": "${{ steps.upstream-metadata.outputs.content_length }}", | |
| "sha256": "$dmg_sha256", | |
| "size_bytes": "$dmg_size_bytes", | |
| "tested_at_utc": "$tested_at_utc", | |
| "cache_schema_version": "${{ env.DMG_CACHE_SCHEMA_VERSION }}" | |
| } | |
| JSON | |
| - name: Build app from upstream DMG | |
| id: acceptance-build | |
| continue-on-error: true | |
| env: | |
| CODEX_ACCEPTANCE_DECISION_JSON: ${{ github.workspace }}/upstream-dmg-decision.json | |
| CODEX_ACCEPTANCE_OVERRIDE: '0' | |
| CODEX_ACCEPTANCE_SOURCE: github-actions | |
| CODEX_UPSTREAM_DMG_METADATA_JSON: ${{ github.workspace }}/upstream-dmg-metadata.json | |
| REBUILD_REPORT_DIR: ${{ github.workspace }}/upstream-acceptance | |
| run: make build-app DMG=/tmp/codex-upstream-ci/Codex.dmg | |
| - name: Import staged Browser clients through node_repl | |
| if: steps.acceptance-build.outcome == 'success' | |
| env: | |
| CODEX_NODE_REPL_PATH: ${{ github.workspace }}/codex-app/resources/node_repl | |
| CODEX_STAGED_BUNDLED_PLUGINS_ROOT: ${{ github.workspace }}/codex-app/resources/plugins/openai-bundled/plugins | |
| run: node --test scripts/lib/browser-client-node-repl-runtime.test.js | |
| - name: Upload upstream DMG metadata artifact | |
| # The transactional installer records a decision before returning a | |
| # rejected status, so diagnostics remain available on failed runs. | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: upstream-dmg-metadata | |
| path: | | |
| upstream-dmg-metadata.json | |
| upstream-dmg-decision.json | |
| upstream-acceptance/**/*.json | |
| if-no-files-found: ignore | |
| - name: Record missing decision in the summary | |
| if: always() | |
| run: | | |
| if [ ! -f upstream-dmg-decision.json ]; then | |
| { | |
| echo "## Upstream DMG acceptance" | |
| echo | |
| echo "No decision artifact was produced; the run is inconclusive." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Enforce shared acceptance decision | |
| if: always() | |
| run: | | |
| test -f upstream-dmg-decision.json | |
| verdict="$(node -e 'console.log(require("./upstream-dmg-decision.json").verdict)')" | |
| case "$verdict" in | |
| accepted|accepted_with_warnings) exit 0 ;; | |
| *) echo "Upstream DMG verdict: $verdict" >&2; exit 1 ;; | |
| esac | |
| reconcile-upstream-dmg-issue: | |
| name: Reconcile Upstream DMG Drift Issue | |
| needs: build-app-upstream-dmg | |
| if: always() && github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Check out trusted reconciliation code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ github.event.repository.default_branch }} | |
| persist-credentials: false | |
| - name: Download acceptance artifact | |
| id: download | |
| continue-on-error: true | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: upstream-dmg-metadata | |
| path: acceptance-artifact | |
| - name: Capture current upstream identity | |
| id: current-identity | |
| if: steps.download.outcome == 'success' && hashFiles('acceptance-artifact/upstream-dmg-decision.json') != '' | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| headers_file="$(mktemp)" | |
| trap 'rm -f "$headers_file"' EXIT | |
| curl -fsSLI "$UPSTREAM_DMG_URL" > "$headers_file" | |
| node - "$headers_file" > current-upstream-identity.json <<'NODE' | |
| const fs = require("node:fs"); | |
| const headers = fs.readFileSync(process.argv[2], "utf8").split(/\r?\n/); | |
| const metadata = {}; | |
| for (const line of headers) { | |
| const match = line.match(/^([^:]+):\s*(.*)$/); | |
| if (!match) continue; | |
| const key = match[1].toLowerCase(); | |
| if (key === "etag") metadata.etag = match[2].replace(/^"|"$/g, ""); | |
| if (key === "last-modified") metadata.last_modified = match[2]; | |
| if (key === "content-length") metadata.content_length = match[2]; | |
| } | |
| process.stdout.write(`${JSON.stringify(metadata, null, 2)}\n`); | |
| NODE | |
| - name: Create, update, or close the drift issue | |
| if: steps.current-identity.outcome == 'success' | |
| uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const { httpIdentity } = require('./scripts/lib/upstream-dmg-acceptance.js'); | |
| const { reconcileUpstreamDmgIssue } = require('./scripts/ci/upstream-dmg-issue.js'); | |
| const decision = JSON.parse(fs.readFileSync('acceptance-artifact/upstream-dmg-decision.json', 'utf8')); | |
| const currentMetadata = JSON.parse(fs.readFileSync('current-upstream-identity.json', 'utf8')); | |
| const result = await reconcileUpstreamDmgIssue({ | |
| github, | |
| repo: context.repo, | |
| decision, | |
| currentHttpIdentityKey: httpIdentity(currentMetadata)?.key ?? null, | |
| }); | |
| core.info(`Upstream DMG issue reconciliation: ${JSON.stringify(result)}`); |