From c5f56864200c02cdf1410febfbfe194346b79d3f Mon Sep 17 00:00:00 2001 From: labgadget015-dotcom Date: Fri, 4 Sep 2026 08:38:17 +0000 Subject: [PATCH] ci: add weekly DRC end-to-end canary Restores a routine end-to-end signal for the intake path. The Event Router drops any sender ending in "[bot]", so the nightly Daily Repository Summary issues never reach the council. Until today the only recurring event that traversed the full path was the PR Triage Pipeline's daily rewrite of issue #164, which re-ran the council on an unchanged premise every morning and has been filtered out. Without a replacement there is no routine proof the path works, and the existing Intake Canary only proves the n8n API is reachable. The assertion inspects the "Store to TIM Postgres" node output rather than the execution status, because that node masks its own errors and reports a green execution while dropping the audit row. Both branches were validated against real executions: the passing case against 7848, and the failing case against 7832, the run that exposed the bug in #284. Uses GH_PAT rather than GITHUB_TOKEN so the canary issue passes the router's bot-sender filter. Costs one council run per week. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017bKHKCuokjG8ZKcizbgRs1 --- .github/workflows/drc-e2e-canary.yml | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/workflows/drc-e2e-canary.yml diff --git a/.github/workflows/drc-e2e-canary.yml b/.github/workflows/drc-e2e-canary.yml new file mode 100644 index 0000000..1f98400 --- /dev/null +++ b/.github/workflows/drc-e2e-canary.yml @@ -0,0 +1,127 @@ +name: DRC End-to-End Canary + +# Proves the whole intake path works: GitHub webhook -> HMAC validation -> +# Event Router -> DRC council -> row in tim.agent_runs. +# +# Why this exists: the Event Router drops senders ending in "[bot]", so the +# nightly Daily Repository Summary issues never reach the council. Until +# 2026-09-04 the only recurring event that traversed the full path was the PR +# Triage Pipeline's daily rewrite of issue #164, which was removed because it +# re-ran the council on an unchanged premise every morning. Without this canary +# there is no routine end-to-end signal, and the Intake Canary only proves the +# n8n API is reachable, not that the path works. +# +# Cost: one council run per week. +# +# The assertion deliberately inspects the "Store to TIM Postgres" node output +# rather than just the execution status. That node masks its own errors with +# onError=continueRegularOutput, so a failed write still reports a green +# execution. That exact failure silently dropped audit rows (issue #284). + +on: + schedule: + - cron: '40 7 * * 1' # Mondays 07:40 UTC, after the 07:00 stale handler + workflow_dispatch: + +permissions: + issues: write + +env: + DRC_WORKFLOW_ID: Wlfhgk4sUfXJcU4D + N8N_BASE: https://gadgetlab.app.n8n.cloud + +jobs: + canary: + name: "Prove the DRC path end to end" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: "Record start time" + id: t0 + run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + + - name: "Open canary issue" + id: open + env: + # GH_PAT, not GITHUB_TOKEN: the Event Router drops any sender whose + # login ends in "[bot]", and GITHUB_TOKEN posts as github-actions[bot]. + GH_TOKEN: ${{ secrets.GH_PAT }} + run: | + set -euo pipefail + body=$(cat <<'EOF' + Automated weekly canary. Safe to close. + + Context: this line is colon-led, the shape that broke the concatenated INSERT. + Quotes: it's got 'single', "double", and \backslashes\ plus a lone $1. + + Expected: one row in tim.agent_runs whose issue_body matches this text. + EOF + ) + url=$(gh issue create \ + --repo "${{ github.repository }}" \ + --title "[DRC CANARY] weekly end-to-end check $(date -u +%Y-%m-%d)" \ + --body "$body") + echo "number=${url##*/}" >> "$GITHUB_OUTPUT" + echo "Opened $url" + + - name: "Wait for a council run and assert the audit write" + id: assert + env: + N8N_API_KEY: ${{ secrets.N8N_API_KEY }} + SINCE: ${{ steps.t0.outputs.value }} + run: | + set -euo pipefail + exec_id="" + for _ in $(seq 1 40); do + exec_id=$(curl -sS -H "X-N8N-API-KEY: $N8N_API_KEY" \ + "$N8N_BASE/api/v1/executions?workflowId=$DRC_WORKFLOW_ID&limit=20" \ + | jq -r --arg since "$SINCE" \ + '[.data[] | select(.startedAt > $since) | select(.status != "running")] + | sort_by(.startedAt) | last | .id // ""') + [ -n "$exec_id" ] && break + sleep 15 + done + + if [ -z "$exec_id" ]; then + echo "::error::No DRC execution completed within 10 minutes of opening the canary issue. Intake is broken." + exit 1 + fi + echo "DRC execution: $exec_id" + + node=$(curl -sS -H "X-N8N-API-KEY: $N8N_API_KEY" \ + "$N8N_BASE/api/v1/executions/$exec_id?includeData=true" \ + | jq -c '.data.resultData.runData["Store to TIM Postgres"][0].data.main[0][0].json // {}') + + # A successful write returns the RETURNING clause. A masked failure + # returns an object carrying "error" while the execution stays green. + if echo "$node" | jq -e 'has("error")' >/dev/null; then + echo "::error::The council ran but the audit write failed silently. $(echo "$node" | jq -r '.message // "no message"')" + exit 1 + fi + if ! echo "$node" | jq -e 'has("id") and has("run_id")' >/dev/null; then + echo "::error::The audit node returned no row id. Output was: $node" + exit 1 + fi + echo "Audit row written: $(echo "$node" | jq -c '{id, run_id, final_recommendation}')" + + - name: "Close the canary issue" + if: always() && steps.open.outputs.number != '' + env: + GH_TOKEN: ${{ secrets.GH_PAT }} + run: | + gh issue close "${{ steps.open.outputs.number }}" \ + --repo "${{ github.repository }}" \ + --reason completed \ + --comment "Canary complete. Result: ${{ steps.assert.outcome }}." + + - name: "Alert Slack on failure" + if: failure() + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + run: | + # The secrets context is not allowed in an `if`, so gate here instead. + [ -n "${SLACK_WEBHOOK_URL:-}" ] || { echo "No Slack webhook configured; skipping alert."; exit 0; } + curl -sS -X POST -H 'Content-Type: application/json' \ + -d "{\"text\":\"DRC end-to-end canary FAILED. The intake path is not delivering audit rows. Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \ + "$SLACK_WEBHOOK_URL"