diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index d31b505..7405386 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -30,7 +30,11 @@ on: required: true type: boolean secrets: - PUBLISH_TOKEN: + BRAINTRUST_BOT_APP_ID: + description: "braintrust-bot GitHub App id; mints the distribution-repo token." + required: true + BRAINTRUST_BOT_PRIVATE_KEY: + description: "braintrust-bot GitHub App private key." required: true OPENAI_API_KEY: description: "For the post-deploy codex smoke test; skipped if unset." @@ -75,12 +79,33 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "dist_tag=$dist_tag" >> "$GITHUB_OUTPUT" + dist_repo="${{ inputs.dist_repo }}" + if ! printf '%s' "$dist_repo" | grep -Eq '^[^/[:space:]]+/[^/[:space:]]+$'; then + echo "::error::dist_repo '$dist_repo' must be owner/name."; exit 1 + fi + echo "dist_owner=${dist_repo%%/*}" >> "$GITHUB_OUTPUT" + echo "dist_name=${dist_repo#*/}" >> "$GITHUB_OUTPUT" echo "Releasing $tag -> ${{ inputs.dist_repo }}@$dist_tag (record=${{ inputs.record }})" + # Cross-repo work runs as the braintrust-bot GitHub App instead of a + # long-lived PAT: the token is minted per run, expires in an hour, and is + # scoped to contents:write on the one distribution repo. Monorepo commits + # and tags still use the built-in GITHUB_TOKEN. This step fails loudly if + # braintrust-bot is not installed on . + - name: Generate distribution-repo token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.BRAINTRUST_BOT_APP_ID }} + private-key: ${{ secrets.BRAINTRUST_BOT_PRIVATE_KEY }} + owner: ${{ steps.vars.outputs.dist_owner }} + repositories: ${{ steps.vars.outputs.dist_name }} + permission-contents: write + - name: Validate distribution release tag if: ${{ inputs.record }} env: - GH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} DIST_REPO: ${{ inputs.dist_repo }} DIST_TAG: ${{ steps.vars.outputs.dist_tag }} run: | @@ -149,13 +174,13 @@ jobs: - name: Deploy to distribution repo env: PUBLISH_TARGETS: "${{ inputs.plugin }}:${{ inputs.dist_repo }}" - GH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: make publish - name: Tag and release distribution repo if: ${{ inputs.record }} env: - GH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} DIST_REPO: ${{ inputs.dist_repo }} DIST_TAG: ${{ steps.vars.outputs.dist_tag }} run: | diff --git a/.github/workflows/smoke-codex.yml b/.github/workflows/smoke-codex.yml index e25492b..929a888 100644 --- a/.github/workflows/smoke-codex.yml +++ b/.github/workflows/smoke-codex.yml @@ -13,7 +13,9 @@ on: required: true type: string secrets: - PUBLISH_TOKEN: + BRAINTRUST_BOT_APP_ID: + required: true + BRAINTRUST_BOT_PRIVATE_KEY: required: true OPENAI_API_KEY: required: false @@ -64,6 +66,18 @@ jobs: - name: Checkout monorepo uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Split dist repo into owner/name + id: dist + env: + DIST_REPO: ${{ inputs.dist_repo }} + run: | + set -euo pipefail + if ! printf '%s' "$DIST_REPO" | grep -Eq '^[^/[:space:]]+/[^/[:space:]]+$'; then + echo "::error::dist_repo '$DIST_REPO' must be owner/name."; exit 1 + fi + echo "owner=${DIST_REPO%%/*}" >> "$GITHUB_OUTPUT" + echo "name=${DIST_REPO#*/}" >> "$GITHUB_OUTPUT" + - name: Set up Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -72,16 +86,27 @@ jobs: - name: Install Codex and bt CLIs run: | npm install -g @openai/codex - curl -fsSL https://bt.dev/cli/install.sh | sh + curl -fsSL https://bt.dev/cli/install.sh | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Require daemon-capable bt run: | "$HOME/.local/bin/bt" trace hook --help + # Read-only marketplace clone, as braintrust-bot rather than a PAT. + - name: Generate distribution-repo token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.BRAINTRUST_BOT_APP_ID }} + private-key: ${{ secrets.BRAINTRUST_BOT_PRIVATE_KEY }} + owner: ${{ steps.dist.outputs.owner }} + repositories: ${{ steps.dist.outputs.name }} + permission-contents: read + - name: Install plugin from ${{ inputs.dist_repo }} env: - GH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/" codex plugin marketplace add "${{ inputs.dist_repo }}" diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 70cb648..4acd7da 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -12,11 +12,11 @@ on: workflow_dispatch: inputs: version: - description: "Release version, e.g. v0.0.4" + description: "Version to stamp into the build, e.g. v0.0.4. NOTE: nothing mutated or tagged in this monorepo" required: true type: string plugin: - description: "Plugin bundle to release" + description: "Plugin bundle to release. Overwrites braintrustdata/test-coding-agent-dist" required: true type: choice options: [antigravity, claude, codex] diff --git a/AGENTS.md b/AGENTS.md index c147f2c..2af3100 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,11 @@ branch revision are cancelled. ## Secrets -- `PUBLISH_TOKEN` grants `contents:write` on distribution repositories. +- `BRAINTRUST_BOT_APP_ID` / `BRAINTRUST_BOT_PRIVATE_KEY` are the + `braintrustdata/braintrust-bot` GitHub App credentials. Release workflows mint + a short-lived installation token from them, scoped to `contents:write` on the + one distribution repository being deployed. The app must be installed on every + distribution repository, or the token step fails. - `OPENAI_API_KEY` enables the optional real Codex smoke test. Braintrust authentication is deliberately not stored in plugin or daemon