diff --git a/.github/actions/slack-post/action.yml b/.github/actions/slack-post/action.yml new file mode 100644 index 0000000..805e0d4 --- /dev/null +++ b/.github/actions/slack-post/action.yml @@ -0,0 +1,73 @@ +name: Post Slack Message +description: Post a Block Kit message to a Slack channel via chat.postMessage + +inputs: + channel: + description: Slack channel to post to (e.g. '#agentic-service-alerts') + required: true + header: + description: Header text for the message (also used as fallback text) + required: true + content: + description: JSON array of Block Kit blocks for the message body + required: true + +runs: + using: composite + steps: + - name: Post to Slack + shell: bash + env: + SLACK_CHANNEL: ${{ inputs.channel }} + SLACK_HEADER: ${{ inputs.header }} + SLACK_CONTENT: ${{ inputs.content }} + GITHUB_REPO: ${{ github.repository }} + GITHUB_SERVER: ${{ github.server_url }} + GITHUB_RUN: ${{ github.run_id }} + run: | + if [ -z "$SLACK_TOKEN" ]; then + echo "::error::SLACK_TOKEN environment variable is not set" + exit 1 + fi + + if ! echo "$SLACK_CONTENT" | jq -e 'type == "array"' > /dev/null 2>&1; then + echo "::error::content input must be a valid JSON array" + exit 1 + fi + + TIMESTAMP=$(date +%s) + TIMESTAMP_FMT=$(date -u +%Y-%m-%dT%H:%M:%SZ) + + PAYLOAD=$(jq -n \ + --arg channel "$SLACK_CHANNEL" \ + --arg header "$SLACK_HEADER" \ + --arg repo "$GITHUB_REPO" \ + --arg server "$GITHUB_SERVER" \ + --arg run_id "$GITHUB_RUN" \ + --argjson content "$SLACK_CONTENT" \ + --argjson ts "$TIMESTAMP" \ + --arg ts_fmt "$TIMESTAMP_FMT" \ + '{ + channel: $channel, + text: $header, + blocks: ( + [ + {type: "header", text: {type: "plain_text", text: $header}}, + {type: "context", elements: [{type: "mrkdwn", text: "<\($server)/\($repo)|\($repo)> | <\($server)/\($repo)/actions/runs/\($run_id)|Run #\($run_id)>"}]} + ] + + $content + + [ + {type: "context", elements: [{type: "mrkdwn", text: "Posted at "}]} + ] + ) + }') + + RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + + if [ "$(echo "$RESPONSE" | jq -r '.ok')" != "true" ]; then + echo "::error::Slack API error: $(echo "$RESPONSE" | jq -r '.error')" + exit 1 + fi diff --git a/.github/workflows/drift-detection.yml b/.github/workflows/drift-detection.yml index 47bf920..5c1c52e 100644 --- a/.github/workflows/drift-detection.yml +++ b/.github/workflows/drift-detection.yml @@ -14,7 +14,13 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: scope3data/actions/node/install@node/install/v1 + - uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci --no-audit --no-fund --ignore-scripts - name: Run drift detection id: drift @@ -49,30 +55,28 @@ jobs: - name: Alert on drift if: steps.drift.outputs.exit_code == '1' + uses: ./.github/actions/slack-post env: SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} - uses: scope3data/actions/slack/post@slack/post/v2 with: channel: '#agentic-service-alerts' header: 'SDK API Drift Detected' content: | [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*${{ steps.parse.outputs.total }} endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: ${{ steps.parse.outputs.skill_missing }}\n• Extra in skill.md: ${{ steps.parse.outputs.skill_extra }}\n\n*SDK vs spec:*\n• Missing from SDK: ${{ steps.parse.outputs.sdk_missing }}\n• Extra in SDK: ${{ steps.parse.outputs.sdk_extra }}" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full report>" - } - } + {"type": "section", "text": {"type": "mrkdwn", "text": "*${{ steps.parse.outputs.total }} endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: ${{ steps.parse.outputs.skill_missing }}\n• Extra in skill.md: ${{ steps.parse.outputs.skill_extra }}\n\n*SDK vs spec:*\n• Missing from SDK: ${{ steps.parse.outputs.sdk_missing }}\n• Extra in SDK: ${{ steps.parse.outputs.sdk_extra }}"}}, + {"type": "section", "text": {"type": "mrkdwn", "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full report>"}} ] + - name: Notify on failure + if: failure() + uses: ./.github/actions/slack-post + env: + SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} + with: + channel: '#agentic-service-alerts' + header: 'API Drift Detection Failed' + content: '[{"type": "section", "text": {"type": "mrkdwn", "text": "The drift detection workflow failed. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"}}]' + regenerate: name: Regenerate Schemas needs: detect-drift diff --git a/.github/workflows/regenerate-schemas.yml b/.github/workflows/regenerate-schemas.yml index 27cf53f..cded268 100644 --- a/.github/workflows/regenerate-schemas.yml +++ b/.github/workflows/regenerate-schemas.yml @@ -24,7 +24,13 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} - - uses: scope3data/actions/node/install@node/install/v1 + - uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci --no-audit --no-fund --ignore-scripts - name: Regenerate schemas run: npm run generate-schemas @@ -81,26 +87,24 @@ jobs: - name: Notify Slack if: steps.create-pr.outputs.pr_url + uses: ./.github/actions/slack-post env: SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} - uses: scope3data/actions/slack/post@slack/post/v2 with: channel: '#agentic-service-alerts' header: 'SDK Schemas Regenerated' content: | [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "A PR has been created to update Zod schemas from the latest OpenAPI specification." - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ steps.create-pr.outputs.pr_url }}|View Pull Request>" - } - } + {"type": "section", "text": {"type": "mrkdwn", "text": "A PR has been created to update Zod schemas from the latest OpenAPI specification."}}, + {"type": "section", "text": {"type": "mrkdwn", "text": "<${{ steps.create-pr.outputs.pr_url }}|View Pull Request>"}} ] + + - name: Notify on failure + if: failure() + uses: ./.github/actions/slack-post + env: + SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} + with: + channel: '#agentic-service-alerts' + header: 'Schema Regeneration Failed' + content: '[{"type": "section", "text": {"type": "mrkdwn", "text": "The schema regeneration workflow failed. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"}}]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c059871..fe7a033 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,3 +51,13 @@ jobs: title: 'chore: version packages' env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Notify on failure + if: failure() + uses: ./.github/actions/slack-post + env: + SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} + with: + channel: '#agentic-service-alerts' + header: 'Release Failed' + content: '[{"type": "section", "text": {"type": "mrkdwn", "text": "The release workflow failed. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"}}]'