From cdb34829b112060a09deb9ec733ba7ae8dc639e9 Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Tue, 14 Apr 2026 10:50:59 -0500 Subject: [PATCH 1/2] fix(ci): replace private scope3data/actions with inline equivalents The scope3data/actions repo is private but agentic-client is public. GitHub blocks public repos from using actions in private repos, causing "Unable to resolve action" failures in drift-detection and regenerate-schemas workflows. Replace scope3data/actions/node/install with actions/setup-node + npm ci. Replace scope3data/actions/slack/post with inline jq + curl to Slack API. --- .github/workflows/drift-detection.yml | 64 ++++++++++++++++-------- .github/workflows/regenerate-schemas.yml | 60 ++++++++++++++-------- 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/.github/workflows/drift-detection.yml b/.github/workflows/drift-detection.yml index 47bf920..45a830b 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 @@ -51,27 +57,41 @@ jobs: if: steps.drift.outputs.exit_code == '1' 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>" - } - } - ] + run: | + TIMESTAMP=$(date +%s) + TIMESTAMP_FMT=$(date -u +%Y-%m-%dT%H:%M:%SZ) + PAYLOAD=$(jq -n \ + --arg channel "#agentic-service-alerts" \ + --arg header "SDK API Drift Detected" \ + --arg repo "${{ github.repository }}" \ + --arg server "${{ github.server_url }}" \ + --arg run_id "${{ github.run_id }}" \ + --arg total "${{ steps.parse.outputs.total }}" \ + --arg skill_missing "${{ steps.parse.outputs.skill_missing }}" \ + --arg skill_extra "${{ steps.parse.outputs.skill_extra }}" \ + --arg sdk_missing "${{ steps.parse.outputs.sdk_missing }}" \ + --arg sdk_extra "${{ steps.parse.outputs.sdk_extra }}" \ + --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)>"}]}, + {type: "section", text: {type: "mrkdwn", text: ("*\($total) endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: \($skill_missing)\n• Extra in skill.md: \($skill_extra)\n\n*SDK vs spec:*\n• Missing from SDK: \($sdk_missing)\n• Extra in SDK: \($sdk_extra)")}}, + {type: "section", text: {type: "mrkdwn", text: "<\($server)/\($repo)/actions/runs/\($run_id)|View full report>"}}, + {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 "Slack API error: $(echo "$RESPONSE" | jq -r '.error')" + exit 1 + fi regenerate: name: Regenerate Schemas diff --git a/.github/workflows/regenerate-schemas.yml b/.github/workflows/regenerate-schemas.yml index 27cf53f..31aa9b3 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 @@ -83,24 +89,34 @@ jobs: if: steps.create-pr.outputs.pr_url 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>" - } - } - ] + run: | + TIMESTAMP=$(date +%s) + TIMESTAMP_FMT=$(date -u +%Y-%m-%dT%H:%M:%SZ) + PAYLOAD=$(jq -n \ + --arg channel "#agentic-service-alerts" \ + --arg header "SDK Schemas Regenerated" \ + --arg repo "${{ github.repository }}" \ + --arg server "${{ github.server_url }}" \ + --arg run_id "${{ github.run_id }}" \ + --arg pr_url "${{ steps.create-pr.outputs.pr_url }}" \ + --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)>"}]}, + {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: "<\($pr_url)|View Pull Request>"}}, + {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 "Slack API error: $(echo "$RESPONSE" | jq -r '.error')" + exit 1 + fi From a912f0fb51d77dced6f732459af030225ab93c1c Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Tue, 14 Apr 2026 11:11:33 -0500 Subject: [PATCH 2/2] feat(ci): add reusable slack-post action and workflow failure notifications Extract duplicated Slack posting logic into a local composite action at .github/actions/slack-post. Add failure notification steps to drift-detection, regenerate-schemas, and release workflows so failures are no longer silent. --- .github/actions/slack-post/action.yml | 73 ++++++++++++++++++++++++ .github/workflows/drift-detection.yml | 54 ++++++------------ .github/workflows/regenerate-schemas.yml | 50 ++++++---------- .github/workflows/release.yml | 10 ++++ 4 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 .github/actions/slack-post/action.yml 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 45a830b..5c1c52e 100644 --- a/.github/workflows/drift-detection.yml +++ b/.github/workflows/drift-detection.yml @@ -55,43 +55,27 @@ 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 }} - run: | - TIMESTAMP=$(date +%s) - TIMESTAMP_FMT=$(date -u +%Y-%m-%dT%H:%M:%SZ) - PAYLOAD=$(jq -n \ - --arg channel "#agentic-service-alerts" \ - --arg header "SDK API Drift Detected" \ - --arg repo "${{ github.repository }}" \ - --arg server "${{ github.server_url }}" \ - --arg run_id "${{ github.run_id }}" \ - --arg total "${{ steps.parse.outputs.total }}" \ - --arg skill_missing "${{ steps.parse.outputs.skill_missing }}" \ - --arg skill_extra "${{ steps.parse.outputs.skill_extra }}" \ - --arg sdk_missing "${{ steps.parse.outputs.sdk_missing }}" \ - --arg sdk_extra "${{ steps.parse.outputs.sdk_extra }}" \ - --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)>"}]}, - {type: "section", text: {type: "mrkdwn", text: ("*\($total) endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: \($skill_missing)\n• Extra in skill.md: \($skill_extra)\n\n*SDK vs spec:*\n• Missing from SDK: \($sdk_missing)\n• Extra in SDK: \($sdk_extra)")}}, - {type: "section", text: {type: "mrkdwn", text: "<\($server)/\($repo)/actions/runs/\($run_id)|View full report>"}}, - {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 "Slack API error: $(echo "$RESPONSE" | jq -r '.error')" - exit 1 - fi + 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>"}} + ] + + - 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 diff --git a/.github/workflows/regenerate-schemas.yml b/.github/workflows/regenerate-schemas.yml index 31aa9b3..cded268 100644 --- a/.github/workflows/regenerate-schemas.yml +++ b/.github/workflows/regenerate-schemas.yml @@ -87,36 +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 }} - run: | - TIMESTAMP=$(date +%s) - TIMESTAMP_FMT=$(date -u +%Y-%m-%dT%H:%M:%SZ) - PAYLOAD=$(jq -n \ - --arg channel "#agentic-service-alerts" \ - --arg header "SDK Schemas Regenerated" \ - --arg repo "${{ github.repository }}" \ - --arg server "${{ github.server_url }}" \ - --arg run_id "${{ github.run_id }}" \ - --arg pr_url "${{ steps.create-pr.outputs.pr_url }}" \ - --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)>"}]}, - {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: "<\($pr_url)|View Pull Request>"}}, - {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 "Slack API error: $(echo "$RESPONSE" | jq -r '.error')" - exit 1 - fi + 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>"}} + ] + + - 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>"}}]'