From 3fd7675ad841380236de67b8839e300664e0c50b Mon Sep 17 00:00:00 2001 From: charly Date: Thu, 15 Oct 2026 13:57:07 +0200 Subject: [PATCH 1/2] feat(ci): install the tag-on-merge caller This repo had no tag-on-merge dispatcher, so merges could land untagged. The file is copied byte-identical from repos whose tags work (sha256 d657e2c1ed7b7343). Org rollout batch 1 of opencharly/.github#204. Assisted-by: DeepSeek Harness ollama-cloud/deepseek-v4.1-flash (analysed on a live system) --- .github/workflows/tag-on-merge.yml | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/tag-on-merge.yml diff --git a/.github/workflows/tag-on-merge.yml b/.github/workflows/tag-on-merge.yml new file mode 100644 index 0000000..f503088 --- /dev/null +++ b/.github/workflows/tag-on-merge.yml @@ -0,0 +1,88 @@ +# ORG-WIDE TAG-ON-MERGE DISPATCHER — installed in every opencharly repo. +name: tag-on-merge + +# Event-driven — no cron (RCA of all previous trigger designs, measured in +# this repo across 8 merges and 17 schedule runs): +# +# 1. `on: push` / `on: pull_request: [closed]` CANNOT see auto-merge merges. +# Native-auto-merge squash merges (actor `app/github-actions`) fire their +# webhook events — they are visible in the public event stream, e.g. +# `21:35:01Z sha=e222762 by=github-actions[bot]` for PR #8 — but GitHub +# Actions creates ZERO workflow runs for them (API-confirmed: no run for +# any merge commit, and 0 `pull_request`-event runs in the workflow's +# whole history). This is platform-level run suppression for auto-merge +# commits; no trigger selection can observe them. (Human merges and +# bot contents-API commits DO create runs — hence `push` is kept as a +# secondary trigger for those paths.) +# 2. `schedule: */15` cron: GitHub cron delivery is best-effort with no SLA. +# Measured 21-44 min gaps (never the configured 15); PR #3's tag landed +# 4+ hours late; PR #7's tag was still missing 30 min after merge. +# 3. `workflow_run` on `charly/pr-validator`: the ONE trigger whose event +# delivery is proven (the 12:12-12:51Z 8/22 runs all received the event; +# they died at startup on a workflow-file issue, not on delivery). +# +# The pr-validator run is the last actor before the merge (its final step +# arms native auto-merge). Firing on its completion and waiting (bounded) for +# the squash to land makes the MERGE the effective gate for tagging. The org +# reusable sweep is idempotent and scans the last 24h of merged PRs, so any +# PR missed by a wait timeout self-heals on the next validator completion. +# The rulebook's add-only backfill remains the ultimate documented path. +on: + workflow_run: + workflows: ["charly/pr-validator"] + types: [completed] + push: + branches: + - main + +permissions: + contents: write + pull-requests: read + +jobs: + wait-for-merge: + if: > + github.event_name == 'push' || + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + - name: Wait for triggering PR to merge (bounded readiness probe) + uses: actions/github-script@v7 + with: + script: | + // Push events: the merged PR is already on main — no wait needed. + if (context.eventName !== 'workflow_run') { + core.info('push event — merged PRs are in the sweep window'); + return; + } + const run = context.payload.workflow_run; + const { data: prs } = await github.rest.pulls.list({ + ...context.repo, + state: 'open', + head: `${context.repo.owner}:${run.head_branch}`, + }); + if (!prs.length) { + core.info('No open PR for head branch — sweep covers already-merged PRs'); + return; + } + const pr = prs[0]; + const deadline = Date.now() + 5 * 60 * 1000; + while (Date.now() < deadline) { + const { data: p } = await github.rest.pulls.get({ + ...context.repo, pull_number: pr.number, + }); + if (p.merged_at) { + core.info(`PR #${pr.number} merged at ${p.merged_at} — proceeding to tag`); + return; + } + await new Promise((r) => setTimeout(r, 20 * 1000)); + } + core.info(`PR #${pr.number} not merged within 5m — sweep still runs; next validator completion self-heals`); + + tag: + if: > + github.event_name == 'push' || + github.event.workflow_run.conclusion == 'success' + needs: wait-for-merge + uses: opencharly/.github/.github/workflows/tag-on-merge.yml@main + secrets: inherit From e6408fa20ed582cc9a8bc908341f92e0963462a4 Mon Sep 17 00:00:00 2001 From: charly Date: Thu, 15 Oct 2026 15:09:29 +0200 Subject: [PATCH 2/2] fix(workflow): make the caller's comment true in this repo, not the one it came from The added caller shipped plugin-box's own RCA in its comment block, so copied verbatim it asserted measurements and PR numbers that are facts about where the file was developed, not about this repository. R1 fixes a finding in-tree when the change itself introduces it, and this change introduced that prose here. Cured at four points (header, trigger RCA attribution, the upstream PR reference, the cron-drift evidence). The file now deliberately differs from plugin-box in this comment block and nowhere else; the comment-only diff is 6 insertions and 6 deletions and the non-comment lines are unchanged. Assisted-by: DeepSeek Harness ollama-cloud/deepseek-v4.1-flash (documentation reviewed) --- .github/workflows/tag-on-merge.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tag-on-merge.yml b/.github/workflows/tag-on-merge.yml index f503088..dda9e6c 100644 --- a/.github/workflows/tag-on-merge.yml +++ b/.github/workflows/tag-on-merge.yml @@ -1,13 +1,13 @@ -# ORG-WIDE TAG-ON-MERGE DISPATCHER — installed in every opencharly repo. +# ORG-WIDE TAG-ON-MERGE DISPATCHER — the org's caller, installed across the opencharly repos. name: tag-on-merge -# Event-driven — no cron (RCA of all previous trigger designs, measured in -# this repo across 8 merges and 17 schedule runs): +# Event-driven — no cron (RCA of all previous trigger designs, measured where +# this caller originated: 8 merges and 17 schedule runs): # # 1. `on: push` / `on: pull_request: [closed]` CANNOT see auto-merge merges. # Native-auto-merge squash merges (actor `app/github-actions`) fire their # webhook events — they are visible in the public event stream, e.g. -# `21:35:01Z sha=e222762 by=github-actions[bot]` for PR #8 — but GitHub +# `21:35:01Z sha=e222762 by=github-actions[bot]` (upstream PR #8) — but GitHub # Actions creates ZERO workflow runs for them (API-confirmed: no run for # any merge commit, and 0 `pull_request`-event runs in the workflow's # whole history). This is platform-level run suppression for auto-merge @@ -15,8 +15,8 @@ name: tag-on-merge # bot contents-API commits DO create runs — hence `push` is kept as a # secondary trigger for those paths.) # 2. `schedule: */15` cron: GitHub cron delivery is best-effort with no SLA. -# Measured 21-44 min gaps (never the configured 15); PR #3's tag landed -# 4+ hours late; PR #7's tag was still missing 30 min after merge. +# Measured 21-44 min gaps (never the configured 15) upstream, where tags +# landed hours late or not at all. # 3. `workflow_run` on `charly/pr-validator`: the ONE trigger whose event # delivery is proven (the 12:12-12:51Z 8/22 runs all received the event; # they died at startup on a workflow-file issue, not on delivery).