diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index f7785d99..684fdd3a 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -23,8 +23,27 @@ on: jobs: + # Capture ONE UTC timestamp shared by both matrix legs below. `--runtime` + # needs the same value on the `main` and `develop` legs so CodeGen.cs comes + # out byte-identical and `develop -> main` release merges don't conflict on + # it. `github.run_started_at` was used for this but resolves to an empty + # string in this reusable-workflow context, so each leg fell back to its own + # `DateTime.UtcNow` and the outputs diverged — capture the value here instead. + get-runtime: + name: Capture shared runtime job + runs-on: ubuntu-latest + outputs: + runtime: ${{ steps.runtime.outputs.runtime }} + steps: + - name: Capture shared runtime step + id: runtime + run: | + set -euo pipefail + echo "runtime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + codegen: name: Run ${{ matrix.target.ref }} codegen and pull request job + needs: [get-runtime] runs-on: ubuntu-latest permissions: contents: write @@ -65,19 +84,20 @@ jobs: token: ${{ steps.app-token.outputs.token }} - name: Run codegen step - # `--runtime` is template-internal hygiene: passing the workflow's - # `run_started_at` to both matrix legs (main and develop) makes them - # produce byte-identical CodeGen.cs, so develop->main release merges - # don't conflict on this demo file every release. Derived projects' - # real codegen should not copy this pattern — if your generator's - # per-run state is intentional, design it not to land on multiple - # release branches simultaneously, or absorb the merge cost. + # `--runtime` is template-internal hygiene: passing the SAME timestamp + # (captured once in the `get-runtime` job above) to both matrix legs + # (main and develop) makes them produce byte-identical CodeGen.cs, so + # develop->main release merges don't conflict on this demo file every + # release. Derived projects' real codegen should not copy this pattern — + # if your generator's per-run state is intentional, design it not to + # land on multiple release branches simultaneously, or absorb the merge + # cost. run: | set -euo pipefail dotnet run --project ./CodeGen/CodeGen.csproj -- \ --codepath ./CodeGen \ --apikey "${{ secrets.NINJA_API_KEY }}" \ - --runtime "${{ github.run_started_at }}" + --runtime "${{ needs.get-runtime.outputs.runtime }}" - name: Format code step run: | diff --git a/AGENTS.md b/AGENTS.md index 5098197b..3ffb096a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ Treat this file as authoritative for everything else; don't restate its rules el - **Bots (Dependabot and codegen) target both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch) and [`.github/workflows/run-codegen-pull-request-task.yml`](./.github/workflows/run-codegen-pull-request-task.yml) runs as a matrix over both branches with branch names `codegen-main` and `codegen-develop`. Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop — both branches receive their updates directly). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` — the same `case` statement covers them. - **Maintainer-pushed commits on a bot PR auto-disable auto-merge.** The merge-bot's `merge-dependabot` and `merge-codegen` jobs only fire on `opened` / `reopened` events (auto-merge is enabled exactly once per PR). When a maintainer pushes commits to a bot's branch (a `synchronize` event with an actor that isn't the same bot), the merge-bot's `disable-auto-merge-on-maintainer-push` job fires and calls `gh pr merge --disable-auto`. The maintainer's commits stay in the PR but won't auto-merge with the bot's content; re-enable auto-merge manually (`gh pr merge --auto ` or the GitHub UI) when ready. - **Why parallel dual-target rather than develop-only with eventual flow-through:** push-distribution channels (HACS for Home Assistant integrations, Linux distros that vendor from `main`, etc.) consume `main` directly. A develop-only model would leave `main` running stale code during long-running develop features. Codegen content can also be production-critical (live API-derived data, language lists, build catalogs) rather than just sample/demo content, so both branches need fresh codegen on their own cadence. -- **Dual-target codegen + per-run state = merge conflicts.** If a generator embeds per-invocation state (timestamps, GUIDs, build IDs) and runs independently on `main` and `develop`, the two branches' outputs diverge and every `develop → main` release conflicts on the generated file. This template's `CodeGen/CodeGen.cs` demo embeds a timestamp; the codegen workflow passes `--runtime "${{ github.run_started_at }}"` to both matrix legs so they produce byte-identical output. **That `--runtime` plumbing is template hygiene only — not a codegen pattern derived projects should reproduce.** Your real generators should either be deterministic given the same inputs (preferred), or not run on both release branches simultaneously, or absorb the per-release merge cost. +- **Dual-target codegen + per-run state = merge conflicts.** If a generator embeds per-invocation state (timestamps, GUIDs, build IDs) and runs independently on `main` and `develop`, the two branches' outputs diverge and every `develop → main` release conflicts on the generated file. This template's `CodeGen/CodeGen.cs` demo embeds a timestamp; the codegen workflow captures one UTC timestamp in a `get-runtime` job and passes it via `--runtime` to both matrix legs so they produce byte-identical output. (It deliberately does *not* use `github.run_started_at`, which resolves to an empty string in the reusable-workflow context and made each leg fall back to its own `DateTime.UtcNow` — diverging the output and reintroducing the conflict.) **That `--runtime` plumbing is template hygiene only — not a codegen pattern derived projects should reproduce.** Your real generators should either be deterministic given the same inputs (preferred), or not run on both release branches simultaneously, or absorb the per-release merge cost. - **App-token workflows use Client ID, not App ID.** `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0; the template uses `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}`. When adding new App-token call sites, use the same form — do not reintroduce `app-id` / `CODEGEN_APP_ID`. See [README "Template - GitHub Setup"](./README.md#template---github-setup) for the secret-setup procedure. ## Release Model