diff --git a/.github/workflows/issue_autodoc.yml b/.github/workflows/issue_autodoc.yml index c1c83e47079d..d3175cd813d4 100644 --- a/.github/workflows/issue_autodoc.yml +++ b/.github/workflows/issue_autodoc.yml @@ -12,6 +12,14 @@ # needed), update (revise an existing page), or create (draft a new page). # The report is posted as a comment on the issue, and update/create drafts # are applied to the dotCMS documentation site through its content API. +# +# Job structure: +# preflight → prep → claude (via ai-workflows/autodoc-executor) → finalize +# +# The claude job uses dotCMS/ai-workflows as a reusable workflow so that the +# OIDC job_workflow_ref claim matches the trust condition on the +# GitHubActions-BedrockCodeReview IAM role. prep and finalize run directly +# in this workflow and do not touch AWS. name: Autodoc — Epic Documentation Audit on: @@ -71,45 +79,33 @@ jobs: echo "should_run=true" >> "$GITHUB_OUTPUT" fi - autodoc: - name: Run documentation audit + prep: + name: Build evaluation context needs: preflight if: needs.preflight.outputs.should_run == 'true' runs-on: ubuntu-latest permissions: - issues: write contents: read - id-token: write + issues: read + outputs: + eval_context: ${{ steps.ctx.outputs.content }} env: EPIC_NUMBER: ${{ github.event.issue.number }} REPO: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - # Used by the Claude subprocess when it executes the curl in burlap.txt — not called directly here. - AUTODOC_DOTCMS_API_TOKEN_AISEARCH: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_AISEARCH }} - AUTODOC_DOTCMS_API_TOKEN_DRAFTING: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_DRAFTING }} - AUTODOC_DOTCMS_BASE_URL: ${{ secrets.AUTODOC_DOTCMS_BASE_URL }} - AUTODOC_DOTCMS_SITE_FOLDER: ${{ secrets.AUTODOC_DOTCMS_SITE_FOLDER }} steps: - name: Checkout triggering repo - uses: actions/checkout@v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.2.2 - name: Checkout dotcms-aios - uses: actions/checkout@v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.2.2 with: repository: dotCMS/dotcms-aios token: ${{ secrets.CI_MACHINE_TOKEN }} path: dotcms-aios - - name: Configure AWS credentials (Bedrock) - if: vars.BEDROCK_ROLE_ARN != '' - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ vars.BEDROCK_ROLE_ARN }} - aws-region: ${{ vars.BEDROCK_AWS_REGION || 'us-east-1' }} - - name: Write prompt run: | cat > /tmp/burlap.txt << 'PROMPTEOF' @@ -214,6 +210,7 @@ jobs: PROMPTEOF - name: Build eval context + id: ctx run: | cat > /tmp/ctx.py << 'PYEOF' import json, os, subprocess, sys @@ -297,7 +294,7 @@ jobs: print(f'warning: no vault file for Epic #{epic_num}', file=sys.stderr) prompt = open('/tmp/burlap.txt').read().strip() - report_path = f'/tmp/Epic-{epic_num}_burlap.md' + report_path = '/tmp/autodoc-report.md' lines += [ '', '---', '', '## Prompt: burlap', '', prompt, '', '---', '', @@ -311,39 +308,57 @@ jobs: PYEOF python3 /tmp/ctx.py - - name: Load eval context - id: eval_context - run: | { echo 'content<<__AUTODOC_EOF__' cat /tmp/eval_context.md echo '__AUTODOC_EOF__' } >> "$GITHUB_OUTPUT" - - name: Compose Claude args - id: claude_args - env: - BEDROCK_MODEL_ID: ${{ vars.BEDROCK_MODEL_ID }} - run: | - ARGS="--allowedTools Bash,Write" - if [ -n "$BEDROCK_MODEL_ID" ]; then - ARGS="--model $BEDROCK_MODEL_ID $ARGS" - fi - echo "value=$ARGS" >> "$GITHUB_OUTPUT" + claude: + name: Run Claude (Bedrock) + needs: prep + permissions: + id-token: write + contents: read + uses: dotCMS/ai-workflows/.github/workflows/autodoc-executor.yml@v3.5.0 + secrets: + AUTODOC_DOTCMS_API_TOKEN_AISEARCH: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_AISEARCH }} + AUTODOC_DOTCMS_API_TOKEN_DRAFTING: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_DRAFTING }} + with: + prompt: ${{ needs.prep.outputs.eval_context }} + bedrock_role_arn: ${{ vars.BEDROCK_ROLE_ARN }} + model_id: ${{ vars.BEDROCK_MODEL_ID }} + autodoc_dotcms_site_folder: ${{ vars.AUTODOC_DOTCMS_SITE_FOLDER }} + autodoc_dotcms_base_url: ${{ vars.AUTODOC_DOTCMS_BASE_URL }} + + finalize: + name: Post report and apply to dotCMS + needs: claude + if: always() && needs.claude.result != 'skipped' + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + + env: + EPIC_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AUTODOC_DOTCMS_API_TOKEN_DRAFTING: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_DRAFTING }} + AUTODOC_DOTCMS_BASE_URL: ${{ vars.AUTODOC_DOTCMS_BASE_URL }} + AUTODOC_DOTCMS_SITE_FOLDER: ${{ vars.AUTODOC_DOTCMS_SITE_FOLDER }} - - name: Run Claude - uses: anthropics/claude-code-action@v1 + steps: + - name: Download report artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - use_bedrock: ${{ vars.BEDROCK_ROLE_ARN != '' && 'true' || 'false' }} - prompt: ${{ steps.eval_context.outputs.content }} - claude_args: ${{ steps.claude_args.outputs.value }} - use_sticky_comment: 'false' - track_progress: 'false' + name: autodoc-report + path: /tmp/ + continue-on-error: true - name: Post report comment run: | - REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + REPORT="/tmp/autodoc-report.md" if [ ! -f "$REPORT" ]; then echo "No report at $REPORT — skipping finalize." @@ -391,7 +406,7 @@ jobs: - name: Apply doc update to dotCMS if: env.ACTION == 'update' run: | - REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + REPORT="/tmp/autodoc-report.md" # Validate urlTitle is a URL slug before interpolating into the JSON query. # Non-slug chars (quotes, backslashes) would break the shell-built JSON payload. @@ -412,6 +427,34 @@ jobs: exit 0 fi + # modUser guardrail: fetch the working inode and check whether it is safe + # to overwrite. GET /api/v1/content/{id} always returns the working inode. + # + # live==true → working IS the live version; no pending draft → safe. + # live==false → a draft exists on top of the published version. + # modUser == service account → automation owns the draft → safe. + # modUser == anyone else → human has an outstanding draft → skip. + if ! CONTENT_CHECK=$(curl -s --fail-with-body \ + "$AUTODOC_DOTCMS_BASE_URL/api/v1/content/$IDENTIFIER" \ + -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_DRAFTING"); then + echo "SKIP: content check request failed — cannot confirm draft ownership. Skipping to avoid clobbering a potential human edit." + exit 0 + fi + IS_LIVE=$(echo "$CONTENT_CHECK" | jq -r '.entity.live // empty') + MOD_USER=$(echo "$CONTENT_CHECK" | jq -r '.entity.modUser // ""') + SERVICE_ACCOUNT='user-3244a6be-d28b-4d80-a3fc-9f66a1d832ae' + + if [ -z "$IS_LIVE" ]; then + echo "SKIP: content check returned unexpected response — cannot confirm draft ownership. Skipping to avoid clobbering a potential human edit." + exit 0 + fi + + if [ "$IS_LIVE" = "false" ] && [ "$MOD_USER" != "$SERVICE_ACCOUNT" ]; then + echo "SKIP: outstanding draft last saved by '${MOD_USER}' (not the CI Autodoc service account)." + echo "The automation will not overwrite a human edit. Please review the draft in dotCMS and apply the proposed changes manually if appropriate." + exit 0 + fi + export REPORT IDENTIFIER python3 << 'PYEOF' import json, os @@ -434,7 +477,7 @@ jobs: - name: Apply doc create to dotCMS if: env.ACTION == 'create' run: | - REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + REPORT="/tmp/autodoc-report.md" # Validate urlTitle is a URL slug before interpolating into the JSON query. if ! echo "$URL_TITLE" | grep -qE '^[a-z0-9][a-z0-9-]*[a-z0-9]$'; then @@ -470,4 +513,3 @@ jobs: -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_DRAFTING" \ -H "Content-Type: application/json" \ --data @/tmp/payload.json -