From db73b880f81d57fa0ae4f18c5c21e74a7a2b7647 Mon Sep 17 00:00:00 2001 From: Jonathan Haas Date: Tue, 28 Apr 2026 15:21:20 -0700 Subject: [PATCH] ci: deepen shared GitHub Actions rails --- .github/workflows/agent-authorship-label.yml | 1 + .github/workflows/codex-rails-check.yml | 24 +++++- profile/AGENT_AUTHORSHIP.md | 4 + test/classify_agent_authorship_test.rb | 83 ++++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 test/classify_agent_authorship_test.rb diff --git a/.github/workflows/agent-authorship-label.yml b/.github/workflows/agent-authorship-label.yml index 39d6dfc..b010509 100644 --- a/.github/workflows/agent-authorship-label.yml +++ b/.github/workflows/agent-authorship-label.yml @@ -27,6 +27,7 @@ permissions: jobs: label: runs-on: ${{ inputs.runner_label }} + timeout-minutes: 10 steps: - name: Checkout org workflow helpers uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 diff --git a/.github/workflows/codex-rails-check.yml b/.github/workflows/codex-rails-check.yml index b953417..cac0f43 100644 --- a/.github/workflows/codex-rails-check.yml +++ b/.github/workflows/codex-rails-check.yml @@ -12,6 +12,7 @@ on: - ".github/workflows/**" - ".github/workflow-templates/**" - "profile/**" + - "test/**" workflow_call: inputs: require_agents: @@ -19,13 +20,19 @@ on: required: false type: boolean default: false + runner_label: + description: "Runner label used for the validation job" + required: false + type: string + default: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read jobs: validate: - runs-on: ubuntu-latest + runs-on: ${{ inputs.runner_label || 'blacksmith-4vcpu-ubuntu-2404' }} + timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 @@ -33,7 +40,7 @@ jobs: shell: bash run: | set -euo pipefail - shopt -s nullglob + shopt -s globstar nullglob files=(.github/ISSUE_TEMPLATE/*.yml .github/ISSUE_TEMPLATE/*.yaml) if [ "${#files[@]}" -eq 0 ]; then echo "No issue template YAML files found." @@ -132,3 +139,16 @@ jobs: end ' "${skill}" done + + - name: Run repo Ruby tests + shell: bash + run: | + set -euo pipefail + shopt -s globstar nullglob + tests=(test/**/*_test.rb test/*_test.rb) + if [ "${#tests[@]}" -eq 0 ]; then + echo "No Ruby tests found." + exit 0 + fi + + ruby -Itest "${tests[@]}" diff --git a/profile/AGENT_AUTHORSHIP.md b/profile/AGENT_AUTHORSHIP.md index 31beb50..fccb232 100644 --- a/profile/AGENT_AUTHORSHIP.md +++ b/profile/AGENT_AUTHORSHIP.md @@ -76,6 +76,10 @@ authorship labels, and applies the label that matches the current PR commit set. It only mutates labels when the desired label set changed, so repeated `synchronize` events do not remove and re-add the same label. +For production repositories, pin the reusable workflow to an immutable +`evalops/.github` commit SHA. When pinning, pass the same SHA as `helper_ref` so +the workflow and helper scripts are resolved from the same reviewed revision. + ## Audit Indexing Audit ingestion should parse trailers from every commit merged to protected diff --git a/test/classify_agent_authorship_test.rb b/test/classify_agent_authorship_test.rb new file mode 100644 index 0000000..eec8a3d --- /dev/null +++ b/test/classify_agent_authorship_test.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "json" +require "minitest/autorun" +require "open3" +require "tempfile" + +class ClassifyAgentAuthorshipTest < Minitest::Test + ROOT = File.expand_path("..", __dir__) + SCRIPT = File.join(ROOT, ".github/scripts/classify-agent-authorship.rb") + + def test_untrailered_commits_are_agent_assisted + outputs = classify([{ "sha" => "abc", "message" => "fix: regular change" }]) + + assert_equal "agent-assisted", outputs.fetch("label") + assert_equal "1", outputs.fetch("total_commits") + assert_equal "0", outputs.fetch("agent_commits") + assert_equal "1", outputs.fetch("untrailered_commits") + assert_equal "0", outputs.fetch("incomplete_agent_commits") + end + + def test_complete_maestro_trailers_are_agent_authored + outputs = classify([{ "sha" => "abc", "message" => <<~MSG }]) + feat: ship change + + Co-Authored-By: Maestro + Maestro-Version: 2026.04.28 / gpt-5 + Maestro-Prompt-Id: prompt-123 + Maestro-Approvals-Id: approval-456 + MSG + + assert_equal "agent-authored", outputs.fetch("label") + assert_equal "1", outputs.fetch("agent_commits") + assert_equal "0", outputs.fetch("untrailered_commits") + assert_equal "0", outputs.fetch("incomplete_agent_commits") + end + + def test_mixed_authorship_and_incomplete_trailers_are_reported + outputs = classify( + [ + { "sha" => "abc", "message" => <<~MSG }, + feat: partial agent change + + Co-Authored-By: Maestro + Maestro-Version: 2026.04.28 / gpt-5 + MSG + { "sha" => "def", "message" => "docs: human follow-up" }, + ], + ) + + assert_equal "mixed-authorship", outputs.fetch("label") + assert_equal "1", outputs.fetch("agent_commits") + assert_equal "1", outputs.fetch("untrailered_commits") + assert_equal "1", outputs.fetch("incomplete_agent_commits") + end + + def test_github_output_file_gets_same_outputs + Tempfile.create("github-output") do |file| + outputs = classify( + [{ "sha" => "abc", "message" => "fix: regular change" }], + github_output: file.path, + ) + file_outputs = parse_outputs(File.read(file.path)) + + assert_equal outputs, file_outputs + end + end + + private + + def classify(commits, github_output: nil) + input = commits.map(&:to_json).join("\n") + args = ["ruby", SCRIPT] + args += ["--github-output", github_output] if github_output + stdout, stderr, status = Open3.capture3(*args, stdin_data: input) + assert status.success?, stderr + parse_outputs(stdout) + end + + def parse_outputs(text) + text.each_line(chomp: true).to_h { |line| line.split("=", 2) } + end +end