API Drift Detection #159
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: API Drift Detection | |
| on: | |
| schedule: | |
| - cron: '0 8 * * 1-5' # 8am UTC, weekdays | |
| workflow_dispatch: | |
| jobs: | |
| detect-drift: | |
| name: Check for API Drift | |
| runs-on: ubuntu-latest | |
| outputs: | |
| drift_detected: ${{ steps.drift.outputs.has_drift }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - 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 | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const { execSync } = require('child_process'); | |
| let stdout = ''; | |
| let exitCode = 0; | |
| try { | |
| stdout = execSync('npm run --silent detect-drift -- --json', { | |
| encoding: 'utf-8', | |
| stdio: ['pipe', 'pipe', 'pipe'], | |
| timeout: 120_000, | |
| }); | |
| } catch (err) { | |
| exitCode = err.status ?? 1; | |
| stdout = err.stdout ?? ''; | |
| const stderr = err.stderr ?? ''; | |
| if (stderr) { | |
| core.info(`--- stderr ---\n${stderr}`); | |
| } | |
| } | |
| core.setOutput('exit_code', String(exitCode)); | |
| core.info(stdout); | |
| if (exitCode === 0) { | |
| core.setOutput('has_drift', 'false'); | |
| return; | |
| } | |
| let report; | |
| try { | |
| report = JSON.parse(stdout); | |
| } catch { | |
| core.warning('Drift report is missing or malformed'); | |
| core.setOutput('has_drift', 'false'); | |
| return; | |
| } | |
| if (!report.hasDrift) { | |
| core.notice(`No actual drift found (totalDrift=${report.totalDrift}), skipping alert`); | |
| core.setOutput('has_drift', 'false'); | |
| return; | |
| } | |
| core.setOutput('has_drift', 'true'); | |
| core.setOutput('total', String(report.totalDrift)); | |
| core.setOutput('skill_missing', String(report.drift.inSpecNotSkill.length)); | |
| core.setOutput('skill_extra', String(report.drift.inSkillNotSpec.length)); | |
| core.setOutput('sdk_missing', String(report.drift.inSpecNotSdk.length)); | |
| core.setOutput('sdk_extra', String(report.drift.inSdkNotSpec.length)); | |
| - name: Alert on drift | |
| if: steps.drift.outputs.has_drift == 'true' | |
| uses: ./.github/actions/slack-post | |
| env: | |
| SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} | |
| with: | |
| channel: '#agentic-service-alerts' | |
| header: 'SDK API Drift Detected' | |
| content: | | |
| [ | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "*${{ steps.drift.outputs.total }} endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: ${{ steps.drift.outputs.skill_missing }}\n• Extra in skill.md: ${{ steps.drift.outputs.skill_extra }}\n\n*SDK vs spec:*\n• Missing from SDK: ${{ steps.drift.outputs.sdk_missing }}\n• Extra in SDK: ${{ steps.drift.outputs.sdk_extra }}"}}, | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full report>"}} | |
| ] | |
| - name: Alert on script error | |
| if: steps.drift.outputs.exit_code != '0' && steps.drift.outputs.has_drift != 'true' | |
| uses: ./.github/actions/slack-post | |
| env: | |
| SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} | |
| with: | |
| channel: '#agentic-service-alerts' | |
| header: 'Drift Detection Script Error' | |
| content: '[{"type": "section", "text": {"type": "mrkdwn", "text": "The drift detection script exited with code ${{ steps.drift.outputs.exit_code || ''unknown'' }} but produced no valid drift report. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"}}]' | |
| - 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 | |
| if: needs.detect-drift.outputs.drift_detected == 'true' | |
| uses: ./.github/workflows/regenerate-schemas.yml | |
| secrets: inherit |