From 3e4bb7c1e48936e414842922bd10dbd43bd3c9b7 Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Fri, 7 Aug 2026 12:01:16 +0800 Subject: [PATCH 1/2] ci(issue-lifecycle): allow issues:write in audit workflow contract The full-repo audit comments violations on issues so they are not an unread step summary. This permits issues:write in the issue-lifecycle.yml contract while keeping contents and pull-requests read-only. Applied separately from the workflow change so the proposed-workflow check can validate the new workflow against the updated contract. --- .github/scripts/test-issue-lifecycle-workflow.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/scripts/test-issue-lifecycle-workflow.rb b/.github/scripts/test-issue-lifecycle-workflow.rb index c65dbbff4..10288e762 100644 --- a/.github/scripts/test-issue-lifecycle-workflow.rb +++ b/.github/scripts/test-issue-lifecycle-workflow.rb @@ -12,8 +12,11 @@ raise "incomplete pull request events" unless events.fetch("pull_request_target").fetch("types").sort == %w[edited opened ready_for_review reopened synchronize] raise "scheduled audit missing" if events.fetch("schedule").empty? -raise "permissions are not minimal and read-only" unless workflow.fetch("permissions") == { - "contents" => "read", "issues" => "read", "pull-requests" => "read" +# The full-repo audit comments violations on issues (issues: write) so they +# are not an unread step summary. The audit workflow is deliberately kept +# otherwise read-only. See issue-lifecycle.yml 'Comment violations on issues'. +raise "permissions not minimal" unless workflow.fetch("permissions") == { + "contents" => "read", "issues" => "write", "pull-requests" => "read" } jobs = workflow.fetch("jobs") From a1fad8406587a030bfd9f88b0f96d2d8272d5773 Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Fri, 7 Aug 2026 12:14:50 +0800 Subject: [PATCH 2/2] ci(issue-lifecycle): accept read or write issues permission in contract Address CodeRabbit review: the assertion must stay aligned with the workflow's actually-declared permissions. Accept issues: read (historical read-only audit) or issues: write (full-repo audit comments violations on issues), while contents and pull-requests remain strictly read-only. --- .github/scripts/test-issue-lifecycle-workflow.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/scripts/test-issue-lifecycle-workflow.rb b/.github/scripts/test-issue-lifecycle-workflow.rb index 10288e762..fbac02044 100644 --- a/.github/scripts/test-issue-lifecycle-workflow.rb +++ b/.github/scripts/test-issue-lifecycle-workflow.rb @@ -12,12 +12,14 @@ raise "incomplete pull request events" unless events.fetch("pull_request_target").fetch("types").sort == %w[edited opened ready_for_review reopened synchronize] raise "scheduled audit missing" if events.fetch("schedule").empty? -# The full-repo audit comments violations on issues (issues: write) so they -# are not an unread step summary. The audit workflow is deliberately kept -# otherwise read-only. See issue-lifecycle.yml 'Comment violations on issues'. -raise "permissions not minimal" unless workflow.fetch("permissions") == { - "contents" => "read", "issues" => "write", "pull-requests" => "read" -} +# The audit workflow keeps contents/pull-requests read-only. `issues` may be +# read (historical/read-only audit) or write (full-repo audit comments +# violations on issues). Accept both so the assertion stays aligned with the +# workflow's actually-declared permissions. +permissions = workflow.fetch("permissions") +raise "permissions not minimal" unless permissions["contents"] == "read" && + permissions["pull-requests"] == "read" && + %w[read write].include?(permissions["issues"]) jobs = workflow.fetch("jobs") raise "expected one audit job" unless jobs.length == 1