[runx] refresh evidence projections #38
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: docs-pr | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| on: | |
| issues: | |
| types: | |
| - opened | |
| - edited | |
| - reopened | |
| issue_comment: | |
| types: | |
| - created | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: Work issue number in this repo. The issue is the living ledger for this docs change. | |
| required: true | |
| type: string | |
| permissions: | |
| actions: write | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| concurrency: | |
| group: docs-pr-${{ github.event.issue.number || github.event.inputs.issue_number || github.ref_name }} | |
| cancel-in-progress: ${{ github.event_name != 'issue_comment' }} | |
| jobs: | |
| docs-pr: | |
| if: >- | |
| ${{ | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event_name == 'issues' && | |
| startsWith(github.event.issue.title, '[docs]') | |
| ) || | |
| ( | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request == null && | |
| github.event.comment.user.type != 'Bot' && | |
| contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) && | |
| !contains(github.event.comment.body, '<!-- aster:runx-skill-lab -->') && | |
| !contains(github.event.comment.body, '<!-- aster:runx-issue-triage -->') && | |
| !contains(github.event.comment.body, '<!-- aster:runx-work-lane:') && | |
| startsWith(github.event.issue.title, '[docs]') | |
| ) | |
| }} | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.ASTER_GH_TOKEN != '' && secrets.ASTER_GH_TOKEN || secrets.RUNX_REPOSITORY_PAT || github.token }} | |
| RUNX_CALLER_MODEL: ${{ vars.RUNX_CALLER_MODEL || 'gpt-5.4' }} | |
| RUNX_CALLER_REASONING_EFFORT: ${{ vars.RUNX_CALLER_REASONING_EFFORT || 'xhigh' }} | |
| RUNX_CALLER_MAX_ATTEMPTS: ${{ vars.RUNX_CALLER_MAX_ATTEMPTS || '3' }} | |
| RUNX_CALLER_REQUEST_TIMEOUT_MS: ${{ vars.RUNX_CALLER_REQUEST_TIMEOUT_MS || '300000' }} | |
| steps: | |
| - name: Check lane prerequisites | |
| id: preflight | |
| env: | |
| HAS_OPENAI_KEY: ${{ secrets.OPENAI_API_KEY != '' }} | |
| run: | | |
| if [ "${HAS_OPENAI_KEY}" != "true" ]; then | |
| echo "enabled=false" >> "$GITHUB_OUTPUT" | |
| echo "OPENAI_API_KEY not configured; skipping docs-pr." | |
| else | |
| echo "enabled=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check out aster | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check out runx | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: ${{ vars.RUNX_REPOSITORY || 'runxhq/runx' }} | |
| ref: ${{ vars.RUNX_REF || 'main' }} | |
| path: .runx/runx | |
| token: ${{ secrets.RUNX_REPOSITORY_PAT || github.token }} | |
| - name: Set up Node.js | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: Enable corepack | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| run: corepack enable | |
| - name: Install and build runx | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| run: | | |
| pnpm --dir .runx/runx install --no-frozen-lockfile | |
| pnpm --dir .runx/runx build | |
| - name: Install scafld | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/nilstate/scafld/main/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| "$HOME/.local/bin/scafld" --version | |
| - name: Configure git author | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Resolve issue envelope | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| id: issue | |
| run: | | |
| mkdir -p .artifacts/docs-pr | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| ISSUE_NUMBER="${{ github.event.inputs.issue_number }}" | |
| else | |
| ISSUE_NUMBER="${{ github.event.issue.number }}" | |
| fi | |
| gh issue view "$ISSUE_NUMBER" \ | |
| --repo "${{ github.repository }}" \ | |
| --json number,title,body,url \ | |
| > .artifacts/docs-pr/issue.json | |
| echo "number=$(jq -r '.number' .artifacts/docs-pr/issue.json)" >> "$GITHUB_OUTPUT" | |
| echo "title<<EOF" >> "$GITHUB_OUTPUT" | |
| jq -r '.title' .artifacts/docs-pr/issue.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| echo "body<<EOF" >> "$GITHUB_OUTPUT" | |
| jq -r '.body // ""' .artifacts/docs-pr/issue.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| echo "url=$(jq -r '.url' .artifacts/docs-pr/issue.json)" >> "$GITHUB_OUTPUT" | |
| - name: Build issue ledger | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| id: issue_ledger | |
| run: | | |
| node scripts/issue-ledger.mjs \ | |
| --repo "${{ github.repository }}" \ | |
| --issue "${{ steps.issue.outputs.number }}" \ | |
| --output .artifacts/docs-pr/issue-ledger.json | |
| echo "ledger_revision=$(jq -r '.ledger_revision' .artifacts/docs-pr/issue-ledger.json)" >> "$GITHUB_OUTPUT" | |
| - name: Prepare work issue request | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| id: request | |
| run: | | |
| node scripts/prepare-work-issue-request.mjs \ | |
| --input .artifacts/docs-pr/issue-ledger.json \ | |
| --lane docs-pr \ | |
| --default-target-repo "${{ github.repository }}" \ | |
| --default-source-repo "${{ github.repository }}" \ | |
| --output .artifacts/docs-pr/work-issue-request.json | |
| echo "request_title=$(jq -r '.request_title' .artifacts/docs-pr/work-issue-request.json)" >> "$GITHUB_OUTPUT" | |
| echo "target_repo=$(jq -r '.target_repo' .artifacts/docs-pr/work-issue-request.json)" >> "$GITHUB_OUTPUT" | |
| - name: Check work issue authorization | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| id: gate | |
| run: | | |
| node scripts/check-thread-teaching-gate.mjs \ | |
| --repo "${{ github.repository }}" \ | |
| --issue "${{ steps.issue.outputs.number }}" \ | |
| --target-repo "${{ steps.request.outputs.target_repo }}" \ | |
| --subject-locator "${{ steps.request.outputs.target_repo }}" \ | |
| --applies-to "docs-pr.publish" \ | |
| --output .artifacts/docs-pr/thread-teaching-gate.json | |
| jq '.context' .artifacts/docs-pr/thread-teaching-gate.json > .artifacts/docs-pr/thread-teaching-context.json | |
| if jq -e '.allowed == true' .artifacts/docs-pr/thread-teaching-gate.json > /dev/null; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "allowed=false" >> "$GITHUB_OUTPUT" | |
| echo "Docs proposal refreshed without draft PR publication." >> "$GITHUB_STEP_SUMMARY" | |
| echo "Reply in the same work issue with trusted \`Applies To:\` + \`Decision:\` lines, or a full thread-teaching record, that authorizes \`docs-pr.publish\` to refresh the draft PR." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Run docs-pr lane | |
| if: ${{ steps.preflight.outputs.enabled == 'true' }} | |
| timeout-minutes: 45 | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| args=( | |
| --lane docs-pr | |
| --runx-root "$GITHUB_WORKSPACE/.runx/runx" | |
| --default-repo "${{ github.repository }}" | |
| --request-file "$GITHUB_WORKSPACE/.artifacts/docs-pr/work-issue-request.json" | |
| --thread-teaching-context-file "$GITHUB_WORKSPACE/.artifacts/docs-pr/thread-teaching-context.json" | |
| --artifact-root "$GITHUB_WORKSPACE/.artifacts/docs-pr" | |
| --work-root "$GITHUB_WORKSPACE/.artifacts/docs-pr-workspaces" | |
| --publish "${{ steps.gate.outputs.allowed == 'true' && 'true' || 'false' }}" | |
| --scafld-bin "$HOME/.local/bin/scafld" | |
| ) | |
| if [ "${{ steps.gate.outputs.allowed }}" != "true" ]; then | |
| args+=(--publish-reason "docs-pr.publish gate not granted yet") | |
| fi | |
| node scripts/run-governed-pr-lane.mjs "${args[@]}" > .artifacts/docs-pr/result-summary.json | |
| cat .artifacts/docs-pr/result-summary.json | |
| - name: Post rolling work issue comment | |
| if: ${{ always() && steps.preflight.outputs.enabled == 'true' }} | |
| run: | | |
| set -euo pipefail | |
| args=( | |
| --repo "${{ github.repository }}" | |
| --issue "${{ steps.issue.outputs.number }}" | |
| --lane docs-pr | |
| --workflow-status "${{ job.status }}" | |
| --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| ) | |
| if [ -n "${{ steps.request.outputs.request_title }}" ]; then | |
| args+=(--request-title "${{ steps.request.outputs.request_title }}") | |
| fi | |
| if [ -n "${{ steps.request.outputs.target_repo }}" ]; then | |
| args+=(--target-repo "${{ steps.request.outputs.target_repo }}") | |
| fi | |
| if [ -n "${{ steps.issue_ledger.outputs.ledger_revision }}" ]; then | |
| args+=(--ledger-revision "${{ steps.issue_ledger.outputs.ledger_revision }}") | |
| fi | |
| if [ -f ".artifacts/docs-pr/result-summary.json" ]; then | |
| args+=(--result-json ".artifacts/docs-pr/result-summary.json") | |
| fi | |
| node scripts/post-work-issue-lane-comment.mjs "${args[@]}" | |
| - name: Publish evidence | |
| if: ${{ always() && steps.preflight.outputs.enabled == 'true' }} | |
| env: | |
| RUNX_PUBLIC_EVIDENCE_API_BASE_URL: ${{ vars.RUNX_PUBLIC_EVIDENCE_API_BASE_URL || 'https://api.runx.ai' }} | |
| RUNX_PUBLIC_EVIDENCE_TOKEN: ${{ secrets.RUNX_PUBLIC_EVIDENCE_TOKEN }} | |
| run: | | |
| STATUS="${{ job.status }}" | |
| TARGET="${{ steps.request.outputs.target_repo || github.repository }}" | |
| PUBLISH_STATUS="" | |
| if [ -f ".artifacts/docs-pr/result-summary.json" ]; then | |
| PUBLISH_STATUS="$(jq -r '.publish.status // ""' .artifacts/docs-pr/result-summary.json)" | |
| fi | |
| if [ "$STATUS" = "success" ] && [ "$PUBLISH_STATUS" = "published" ]; then | |
| TITLE="opened a docs PR on ${TARGET}" | |
| SUMMARY="drafted a bounded docs improvement from the work issue ledger and refreshed one review-ready PR" | |
| elif [ "$STATUS" = "success" ]; then | |
| TITLE="refreshed a docs proposal on ${TARGET}" | |
| SUMMARY="drafted a bounded docs improvement from the work issue ledger without refreshing a draft PR" | |
| else | |
| TITLE="docs PR failed on ${TARGET}" | |
| SUMMARY="docs-pr lane failed during execution" | |
| fi | |
| curl --fail --silent --show-error \ | |
| -X POST \ | |
| -H "authorization: Bearer ${RUNX_PUBLIC_EVIDENCE_TOKEN}" \ | |
| -H "content-type: application/json" \ | |
| --data "$(jq -n \ | |
| --arg title "$TITLE" --arg summary "$SUMMARY" --arg status "$STATUS" \ | |
| --arg repo "$TARGET" --arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ | |
| '{events: [{kind: "aster_run", source: "github-actions", status: (if $status == "success" then "success" else "failure" end), title: $title, summary: $summary, repo: $repo, workflow: "docs-pr", url: $url, timestamp: (now | todate), metadata: {lane: "docs-pr", feed_channel: "main", main_feed_eligible: true}}]}')" \ | |
| "${RUNX_PUBLIC_EVIDENCE_API_BASE_URL%/}/v1/admin/public/evidence" || true | |
| - name: Upload artifacts | |
| if: ${{ always() && steps.preflight.outputs.enabled == 'true' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: docs-pr-${{ steps.issue.outputs.number || github.event.issue.number || github.event.inputs.issue_number }} | |
| path: .artifacts/docs-pr |