name: Convoy metrics gate # Forces convoy PRs to include role-event telemetry. Without this gate, the # pipeline produces work but no signal — exactly what happened during the # Jun 5-11 experiment window when 8 convoy PRs shipped without logging a # single .metrics.jsonl row (see analytics/v0.4-beta1-results.md in # agent-pipeline). # # Triggers: PR titled `convoy:` (case-insensitive prefix). If a convoy PR # does NOT add at least one new role-event line to .convoys/.metrics.jsonl, # this check fails. Bypass: add the `skip-metrics` label to the PR (rare, # document the reason in the PR body). # # Other PRs are no-op (skip the gate). on: pull_request: branches: [main] types: [opened, synchronize, reopened, edited, labeled, unlabeled] permissions: contents: read pull-requests: read jobs: gate: name: Require role-event telemetry on convoy PRs runs-on: [self-hosted, axiom] steps: - name: Inspect PR id: inspect uses: actions/github-script@v7 with: script: | const pr = context.payload.pull_request; const title = pr.title || ''; const labels = (pr.labels || []).map(l => l.name); const isConvoy = /^convoy:/i.test(title); const hasBypass = labels.includes('skip-metrics'); core.setOutput('is_convoy', isConvoy ? 'true' : 'false'); core.setOutput('has_bypass', hasBypass ? 'true' : 'false'); core.info(`title="${title}" is_convoy=${isConvoy} has_bypass=${hasBypass}`); if (hasBypass) { core.warning(`skip-metrics label present on PR #${pr.number} — gate will exit early. Document the reason in the PR body.`); } - name: Skip — not a convoy PR if: steps.inspect.outputs.is_convoy != 'true' run: | echo "PR title is not 'convoy:' — gate is no-op for this PR." - name: Skip — bypass label present if: steps.inspect.outputs.is_convoy == 'true' && steps.inspect.outputs.has_bypass == 'true' run: | echo "::warning::skip-metrics label bypass. Metrics gate not enforced for this PR." - name: Checkout PR head if: steps.inspect.outputs.is_convoy == 'true' && steps.inspect.outputs.has_bypass != 'true' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Verify metrics rows added if: steps.inspect.outputs.is_convoy == 'true' && steps.inspect.outputs.has_bypass != 'true' run: | set -euo pipefail BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" METRICS=".convoys/.metrics.jsonl" if [ ! -f "$METRICS" ]; then echo "::error::Convoy PR but $METRICS does not exist. The L2 roles must call scripts/log-convoy-event.sh after each hand-off — see .cursor/agents/role-*.md 'Metrics' sections." exit 1 fi ADDED_LINES=$(git diff "$BASE_SHA"..."$HEAD_SHA" -- "$METRICS" | grep -c '^+{' || true) if [ "$ADDED_LINES" -eq 0 ]; then echo "::error::Convoy PR did not add any rows to $METRICS." echo "::error::Did the L2 roles call scripts/log-convoy-event.sh after their hand-off? See .cursor/agents/role-*.md 'Metrics' section." echo "::error::To bypass intentionally (rare), add 'skip-metrics' label to the PR and document why in the PR body." exit 1 fi if ! git diff "$BASE_SHA"..."$HEAD_SHA" -- "$METRICS" | grep -q '"convoy"'; then echo "::warning::Added lines do not appear to be well-formed JSON with a 'convoy' field. Verify scripts/log-convoy-event.sh ran cleanly." fi echo "::notice::Convoy metrics gate passed: $ADDED_LINES new role-event row(s) added to $METRICS."