learningequality / learningequality/.github

Sync automation-template.yml to consumer repos with pull requests

Open
#95 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

github_actions
Dominant language
JavaScript
Stars
1
Forks
7
PR merge metrics
No merged PRs in 30d

Description

Overview

Consumer repos hold a copied automation-template.yml. When the generated template changes, every
consumer must copy it again, and nothing today detects that or does it. A stale copy fails silently.

Add a workflow in this repo that opens a pull request in each consumer repo whose copy no longer
matches the template, in the same shape dependabot uses. The workflow proposes the change. A person
in that repo reviews and merges it.

Complexity: Medium
Target branch: main

Context

#88 gives every consumer one file to copy, .github/workflows/automation.yml, generated from
automation-registry.yml. Most registry changes reach consumers on their own, because that file only
says uses: learningequality/.github/.github/workflows/automation.yml@main. Toggling an automation,
changing a dispatch if:, or pointing one at a different leaf all propagate with no consumer action.

Four things leave a consumer's copy out of date:

  1. A new event or activity type enters the on: union.
  2. The permissions union widens.
  3. The secret list changes.
  4. The consumer's own tooling rewrites the copy.

The fourth is not theoretical. It happened twice during #88, before any migration. yamlfmt v0.16.0
stripped a blank line from the header, and the template lacked the inline
# zizmor: ignore[dangerous-triggers] that the callers it replaces carry. Both were fixed in #88.
Neither was visible from this repo, because this repo pins an older yamlfmt, excludes the generated
files from it, and runs no zizmor.

All four fail quietly. The consumer keeps running its old on: block, so a new automation simply
never fires there. There is no error and no log line.

All four also end in the same state: the consumer's file differs from automation-template.yml. One
mechanism resolves all of them, rather than engineering each cause away separately.

The Change

Add a workflow in this repo that compares each consumer's .github/workflows/automation.yml against
automation-template.yml on main, and opens a pull request replacing it wherever the two differ.

Follow the dependabot shape. The pull request is the whole artifact. There is no accompanying issue,
because the change is a byte copy of one file with nothing to author and nothing to decide, and a
second artifact per repo per change is noise.

The work must be done by the workflow itself, not delegated to an agent. The task is deterministic,
and routing it through an agent means the fix lands only if that agent picks it up, which reintroduces
the silent drift this workflow exists to end.

A person approves and merges every change. This is a constraint on how the workflow is written:

  • It opens pull requests on a branch, and never commits to a default branch.
  • It never merges, never enables auto-merge, and never approves its own pull request.
  • Each consumer repo's existing review rules gate the change, unchanged.
  • A pull request left unmerged stays unmerged. The workflow updates it and never routes around it.

It must be idempotent. A repo already in sync gets nothing. A repo with a sync pull request already
open gets that pull request updated, not a second one.

Detection then falls out of remediation. No open pull request means no drift.

Verify the app installation scope first. The workflows authenticate as
learning-equality-bot[bot], the app behind LE_BOT_APP_ID. That is a different identity from
rtibblesbot, the user account that opened the migration pull requests for #88, so the migration is
no evidence of the app's scope. The app needs contents: write and pull-requests: write on all
eight consumers. If it lacks them on one repo, the workflow silently covers seven. Confirm before
writing code.

Note that contents: write is also enough to push directly to a default branch. Default-branch
protection on each consumer is what makes the human-in-the-loop constraint enforceable rather than
merely intended, so confirm that alongside the app scope.

Report the toolchain conflict rather than looping. If a consumer's own tooling rewrites the copied
file, the sync pull request is reverted and reopened on every run, forever. That means the template is
not stable under that repo's toolchain, which is a defect in the template. Detect the repeat and
report it somewhere a person reads. This is the one case that needs a human-readable report rather
than another pull request.

Consumer repos, from the org-wide search run for #88:

  • kolibri, studio, ricecooker, kolibri-design-system, le-utils
  • kolibri-data-portal, morango, kolibri-installer-debian
Out of Scope
  • Generating the on: block with every activity type for the events in use. It would stop new
    activity types from changing the template, but consumers would then run the workflow on more events
    with every job skipping. pull_request_target: synchronize alone fires on every push to every pull
    request. Sync pull requests make this unnecessary, and the on: block stays minimal.
  • Replacing the computed permissions union with a fixed ceiling, for the same reason.
  • Auto-merging the sync pull requests.
  • Bumping this repo's yamlfmt pin from v0.14.0 to v0.16.0. It would catch the formatting class of
    problem locally and match le-utils, but it also reformats .github/dependabot.yml and
    automation-registry.yml.
Acceptance Criteria
Prerequisites
  • learning-equality-bot[bot] is confirmed to hold contents: write and pull-requests: write
    on all eight consumer repos, and the confirmation is recorded on this issue.
  • Default-branch protection is confirmed on all eight, so a direct push cannot bypass review.
Human in the loop
  • The workflow opens pull requests on a branch and never commits to a default branch.
  • It never merges a pull request, never enables auto-merge, and never approves one.
  • Each consumer's existing review rules gate the change, unchanged.
  • The pull request body says what changed and that the file is generated, not hand-edited.
Behaviour
  • The workflow runs on a schedule, on workflow_dispatch, and when automation-template.yml
    changes on main.
  • It opens a pull request in each consumer repo whose .github/workflows/automation.yml differs
    from the template, replacing that file and nothing else.
  • No accompanying issue is opened.
  • A repo already in sync gets no pull request.
  • A repo with an open sync pull request gets it updated, never duplicated.
  • Archived repos are skipped. kolibri-app, kolibri-installer-android and kolibri-image-pi
    hold inert copies, because Actions do not run on archived repos.
  • A repo with no automation.yml is reported as not migrated, and gets no pull request.
  • A sync pull request reverted and reopened more than once is reported as a toolchain conflict,
    and is not reopened again until a person acts.
  • A repo the workflow cannot write to is reported, not skipped silently.
  • docs/automation.md says the sync workflow exists, what it opens, who merges it, and what a
    toolchain conflict report means.
Testing
  1. Run the workflow manually while every consumer is in sync. Confirm that it opens nothing.
  2. Change automation-registry.yml so the template changes, merge it, and confirm that the workflow
    opens one pull request per consumer, and merges none of them.
  3. Run it again before merging those. Confirm that the existing pull requests are updated and no
    duplicates appear.
  4. Leave a sync pull request unmerged across two runs. Confirm that the consumer's file is unchanged
    on its default branch.
  5. Merge one consumer's pull request by hand, run again, and confirm that repo is reported in sync.
  6. Revert a sync pull request's change in a consumer repo, then run twice. Confirm that the second
    run reports a toolchain conflict instead of reopening.
  7. Point the workflow at a repo the app cannot write to. Confirm that it reports the failure rather
    than passing.
References
  • #88 introduces the template, the registry and the generator.
  • #86 is the consolidation tracking issue, including the decision against secrets: inherit.
  • docs/automation.md describes the entry point and the cases that need a re-copy.
  • The two toolchain rewrites found during #88: yamlfmt stripping the header blank line, and the
    missing inline zizmor ignore.

AI usage

I used Claude Code while reviewing #88, where the two toolchain rewrites and this propagation gap
surfaced, and to draft this issue from that work. I verified the failure modes against the consumer
repos, chose the dependabot shape over detection alone and over routing the work through an agent,
set the human-in-the-loop constraint, and edited the wording.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

First verify the learningequality-bot app scope and default-branch protection for the eight consumers. Read automation-template.yml, automation-registry.yml, docs/automation.md, and the existing Dependabot workflow shape; then trace how the workflow should compare and update consumer copies. Done means idempotent pull requests, human review, reporting for missing or unwritable repos and toolchain conflicts, documentation, and the listed manual checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions
Domain
ci-cd, devops
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.