Skip to content

Host the Release Chain and the Docker Core in the Hub - #762

Merged
ptr727 merged 25 commits into
developfrom
feature/release-chain-tasks
Aug 16, 2026
Merged

Host the Release Chain and the Docker Core in the Hub#762
ptr727 merged 25 commits into
developfrom
feature/release-chain-tasks

Conversation

@ptr727

@ptr727 ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

What

Hosts the fleet's release chain and Docker core in the hub, per docs/reusable-workflows.md
"Stage 4: The Release Chain and the Docker Core".

  • .github/workflows/build-release-task.yml (new hub task): inlines get-version and
    validate-release, per-target jobs for build-executable, build-nuget, build-pypi (each a
    hook resolved to the caller's own composite action or a hub default), a duplicated Docker core
    as build-docker (docker-prepare / docker-build-base hooks), and github-release verbatim in
    shape (pattern download, exists gate, target_commitish, consume-then-delete,
    expect_release_assets).
  • .github/workflows/build-docker-task.yml (new hub task): the same Docker core, published
    separately for a caller that wants only the Docker leg. The two files carry the same job body by
    hand, since a hub task cannot reach a sibling hub task by a ./ path.
  • Four hub-default composite actions: build-executable-default, build-nuget-default,
    build-pypi-default, docker-prepare-default, authored from today's leaf catalog snippets.
  • The hub's own publish-release.yml now calls build-release-task.yml with every target
    disabled and expect_release_assets: false, exercising the task's get-version,
    validate-release, and github-release jobs on the hub's own release.
  • Retires the five catalog snippets the new hub tasks replace
    (build-release-task.yml, build-executable-task.yml, build-nugetlibrary-task.yml,
    build-pypilibrary-task.yml, build-docker-task.yml), updates the catalog README, and adds a
    retire gap disposition to spec/divergences.json naming the ten carriers.
  • Gives publish-release.yml an interface contract in spec/files.json
    (requiredJobKeys: ["plan", "validate", "publish"]) with new _selftest cases in spec/audit.py, and
    updates spec/fidelity-model.md's "The Workflow Override Seam Contract" paragraph for the
    stub-era shape.
  • Documents the caller-stub shape under a new "Adopting the Release Chain" section, and updates
    the Rollout tracker and TODO.md with the settled design decisions and proof items.
  • Rebased onto #758's LF-default flip. Every file this PR touches is LF now.

Design choices where the brief left room

  • Docker core duplication. build-release-task.yml's build-docker job and
    build-docker-task.yml carry the same job body rather than one calling the other, since a hub
    task cannot nest a sibling hub task by a ./ path. build-docker-task.yml stays published for
    a caller that wants the Docker leg alone.
  • No publish-release-task.yml. A caller stub's plan/validate/publish/publish-pypi
    jobs are each a thin call to one hub task or a verbatim OIDC upload, and the trigger policy
    tying them together differs enough across the fleet's shapes (dispatch-only Docker schedule,
    push-gated NuGet/PyPI) that hosting it would only move the same with: block, not remove it.
  • Project-path inputs on the executable/nuget/pypi hub defaults. Beyond the fixed
    ref/branch/smoke set, the defaults take project-file/project-dir, mirroring the
    Docker hook's own image input, since no live repo's project folder matches the vanilla
    Console/NuGetLibrary/PyPiLibrary convention literally.
  • release-assets hook deferred. No cataloged repo needs an "extra files" hook today, so it
    is not shipped. The Hook Catalog table row for build-release-task.yml reflects only the
    hooks actually implemented.
  • actionlint's job_workflow_sha gap. github.job_workflow_sha (used to check out the hub at
    the caller's pinned commit) is a real GitHub Actions context property actionlint 1.7.12 does not
    yet recognize. Added a scoped -ignore flag to the hub's own validate-task.yml actionlint
    step rather than working around the design.

What this cannot prove from the hub

The hub has no build targets, so every behavioral claim (the hook fallback path, OIDC NuGet
publishing, the Docker matrix/build-base hooks, a real release through the new task) is a proof
item on the PhotoCleaner/PlexCleaner pilot adoption, tracked in docs/reusable-workflows.md
Rollout Stage 4. Adoption itself is out of scope for this PR.

Verification

spec/validate.py, python3 -m unittest discover -s scripts/tests, spec/audit.py --selftest,
spec/workflow_reuse.py --selftest, scripts/repo_gate.py, scripts/build_dist.py --check,
scripts/prose_lint.py (full check set), ruff check/format --check, mypy, actionlint
(with the one documented -ignore), markdownlint-cli2, and editorconfig-checker all pass.

Copilot AI lite review requested due to automatic review settings August 16, 2026 13:23
ptr727 added a commit that referenced this pull request Aug 16, 2026
Records #762 as the evidence for the Rollout tracker release-chain hub pull request checkbox.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves the fleet’s release-chain orchestration and Docker “core” implementation into hub-hosted reusable workflows, backed by hub-default hook actions, and updates the spec/docs/audit machinery to reflect the new stub-based adoption model.

Changes:

  • Adds hub workflows for build-release-task.yml (release chain + duplicated Docker core) and build-docker-task.yml (Docker core only), plus hub-default composite actions for build hooks and docker-prepare.
  • Updates the hub’s publish-release.yml to exercise the new hub release task (with all targets disabled) and updates spec/docs/audit artifacts for the “caller stub” model.
  • Retires the prior catalog workflow snippets that the new hub tasks replace and records the retirement in divergences/prose-lint.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TODO.md Updates Stage 4 backlog item with settled decisions for the hub-hosted release chain + Docker core.
spec/files.json Changes publish-release.yml to an interface-checked stub contract.
spec/fidelity-model.md Documents the contract shift from carried job graphs to caller stubs.
spec/divergences.json Adds a retire disposition for downstream build-release-task.yml copies after adoption.
spec/audit.py Adds selftests for the new publish-release.yml interface contract shape.
scripts/prose_lint.py Adds build-release-task.yml to the hub-hosted path allowlist for prose linting.
docs/reusable-workflows.md Documents “Adopting the Release Chain” and updates Stage 4 scope and rollout/proof items.
catalog/snippets/workflows/README.md Updates catalog description to reflect hub-hosted tasks and moves hooks to composite actions.
catalog/snippets/workflows/build-release-task.yml Removes retired snippet (replaced by hub workflow).
catalog/snippets/workflows/build-pypilibrary-task.yml Removes retired snippet (replaced by hook default / hub task).
catalog/snippets/workflows/build-nugetlibrary-task.yml Removes retired snippet (replaced by hook default / hub task).
catalog/snippets/workflows/build-executable-task.yml Removes retired snippet (replaced by hook default / hub task).
catalog/snippets/workflows/build-docker-task.yml Removes retired snippet (replaced by hub Docker task).
.github/workflows/validate-task.yml Adds an actionlint ignore for github.job_workflow_sha.
.github/workflows/publish-release.yml Switches hub publishing to call the new build-release-task.yml with all targets disabled.
.github/workflows/build-release-task.yml Adds the hub-hosted release chain workflow with hook-based build targets and embedded Docker core + GitHub release job.
.github/workflows/build-docker-task.yml Adds the hub-hosted Docker-only workflow (Docker core).
.github/actions/docker-prepare-default/action.yml Adds the hub-default docker-prepare hook implementation.
.github/actions/build-pypi-default/action.yml Adds the hub-default build-pypi hook implementation.
.github/actions/build-nuget-default/action.yml Adds the hub-default build-nuget hook implementation.
.github/actions/build-executable-default/action.yml Adds the hub-default build-executable hook implementation.
.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/release-publish-mechanics.md Regenerated/updated skill reference to match the new hub-hosted model.
.claude-plugin/fleet-skills/.source-digest Updates plugin digest for the regenerated content.
.agents/skills/operational-vs-release-workflow/references/release-publish-mechanics.md Updates the source skill reference to match the new hub-hosted model.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/build-release-task.yml
Comment thread .github/workflows/build-release-task.yml Outdated
Comment thread spec/files.json Outdated
Comment thread .github/workflows/publish-release.yml
Copilot AI review requested due to automatic review settings August 16, 2026 13:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (3)

.github/workflows/build-release-task.yml:401

  • build-docker is intended to run even when optional dependencies are skipped (e.g. docker_matrix passed => docker-prepare skipped; or other targets disabled on smoke). Without always(), GitHub Actions will skip this job when any needs job is skipped, so Docker builds can be silently skipped on smoke/partial target sets.
  build-docker:
    name: Build Docker image job
    needs: [get-version, validate-release, build-executable, build-nuget, build-pypi, docker-prepare, docker-build-base]
    if: >-
      ${{ inputs.enable_docker && !failure() && !cancelled() &&
      (needs.docker-prepare.result == 'success' || needs.docker-prepare.result == 'skipped') &&
      (needs.docker-build-base.result == 'success' || needs.docker-build-base.result == 'skipped') }}

spec/files.json:33

  • This interface contract requires publish-release.yml to have a plan job and to reference publish-plan-task.yml, but the hub currently does not ship .github/workflows/publish-plan-task.yml and the hub's own .github/workflows/publish-release.yml has only validate + publish. As written, the hub will fail its own audit and downstream stubs cannot reference the missing hub task.
        { "path": ".github/workflows/validate-task.yml", "fidelity": "intent", "intentRef": "WORKFLOW.md#d1---pr-fast-feedback-smoke", "appliesTo": "*" },
        { "path": ".github/workflows/publish-release.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["plan", "publish"], "requireTokensInJob": { "plan": ["publish-plan-task.yml"], "publish": ["build-release-task.yml"] } }, "intentRef": "WORKFLOW.md#d4---release--publish", "appliesTo": ["two-phase", "dispatch-only", "publish-on-merge"] },
        { "path": ".github/workflows/merge-bot-pull-request.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["merge-bot"], "requireTokensInJob": { "merge-bot": ["merge-bot-task.yml", "CODEGEN_APP_CLIENT_ID", "CODEGEN_APP_PRIVATE_KEY"] } }, "intentRef": "WORKFLOW.md#d8---bots--automation", "appliesTo": "*" },

.github/actions/docker-prepare-default/action.yml:36

  • The hub default docker-prepare emits tags/cache refs from inputs.image without validating it is non-empty. If a caller forgets to set image, this produces invalid tags like docker.io/:latest and fails later with a confusing Docker error.
        set -Eeuo pipefail
        moving=develop
        [[ "$BRANCH" == "main" ]] && moving=latest
        matrix=$(jq -cn \

Copilot AI review requested due to automatic review settings August 16, 2026 13:40
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the three suppressed (collapsed, low-confidence) findings from the round-2 review, since they carry no thread to reply on directly.

Suppressed finding, .github/workflows/build-release-task.yml:401 ("build-docker is intended to run even when optional dependencies are skipped... Without always(), GitHub Actions will skip this job when any needs job is skipped"):
Disproved. Same reasoning as the open thread on this line: an explicit job-level if: (here !failure() && !cancelled() && ...) replaces the implicit success-of-every-need default rather than being ANDed with it, so a skipped need does not skip the dependent without any always(). Inherited verbatim from the live catalog snippet.

Suppressed finding, spec/files.json:33 (the hub's own publish-release.yml lacks a plan job, so it fails the new interface contract):
Accepted, tracked, not fixed here. Same finding as the open thread on spec/files.json:32. The hub's own plan job depends on publish-plan-task.yml, which is #759's deliverable (a parallel same-round PR) and does not exist on develop yet. Tracked as an Open item in TODO.md's release-chain cluster in f11241d, resolved once #759 merges.

Suppressed finding, .github/actions/docker-prepare-default/action.yml:36 ("emits tags/cache refs from inputs.image without validating it is non-empty... produces invalid tags like docker.io/:latest"):
Fixed in f11241d. Added a fail-fast ::error:: when IMAGE is empty, naming both remedies (set docker_image, or carry a caller docker-prepare hook).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.

Suppressed comments (7)

.github/workflows/build-release-task.yml:248

  • Same issue here: github.job_workflow_sha may be missing at runtime for reusable workflows, causing the hub checkout for defaults to fail. Prefer ${{ job.workflow_sha }} inside the reusable workflow.
          ref: ${{ github.job_workflow_sha }}

.github/workflows/build-release-task.yml:298

  • Same issue here: using github.job_workflow_sha risks an empty ref and a failed hub checkout. Use ${{ job.workflow_sha }} for the called-workflow SHA.
          ref: ${{ github.job_workflow_sha }}

.github/workflows/build-release-task.yml:341

  • Same issue here: github.job_workflow_sha is not consistently populated; if it’s blank, the .hub checkout will fail and Docker matrix resolution defaults won’t work. Use ${{ job.workflow_sha }}.
          ref: ${{ github.job_workflow_sha }}

spec/files.json:32

  • This new publish-release.yml interface contract requires a plan job that names publish-plan-task.yml, but the hub’s .github/workflows/publish-release.yml in this PR has only validate + publish (and there is no .github/workflows/publish-plan-task.yml in-tree). As a result, spec/audit.py will report the hub as nonconformant for any selector set that includes two-phase/dispatch-only/publish-on-merge (which includes ProjectTemplate itself).
        { "path": ".github/workflows/publish-release.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["plan", "publish"], "requireTokensInJob": { "plan": ["publish-plan-task.yml"], "publish": ["build-release-task.yml"] } }, "intentRef": "WORKFLOW.md#d4---release--publish", "appliesTo": ["two-phase", "dispatch-only", "publish-on-merge"] },

docs/reusable-workflows.md:310

  • The example publish-pypi job depends on build, but the stub shown above defines publish (the hub build-release-task.yml call) as the job that produces the pypilibrary-build-* artifact. As written, this example won’t run because build doesn’t exist.
    needs: [validate, build]

.github/workflows/validate-task.yml:48

  • These comments refer to a job_workflow_sha property, but the reusable-workflow SHA is exposed via the job context (job.workflow_sha). The docs and code should describe the actual context used so future edits don’t perpetuate the wrong property name.
      # The job_workflow_sha context property resolves the exact commit a caller pinned a hub task to.
      # It is the mechanism docs/reusable-workflows.md "Target Model" uses to check out the hub for a hook default.
      # The pinned actionlint 1.7.12 does not yet recognize this real GitHub context property.
      # The one finding it produces for that field is ignored here rather than silenced by weakening the workflow.

.github/workflows/validate-task.yml:52

  • If the workflows switch to ${{ job.workflow_sha }} (instead of github.job_workflow_sha), this ignore string won’t match the linter message anymore. Update (or remove) the ignore so it continues to be scoped to the one intended warning.
        with:
          flags: -ignore 'property "job_workflow_sha" is not defined'

Comment thread .github/workflows/build-release-task.yml Outdated
Comment thread .github/workflows/build-docker-task.yml Outdated
Copilot AI review requested due to automatic review settings August 16, 2026 13:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (4)

.github/workflows/build-docker-task.yml:101

  • This uses ref: ${{ job.workflow_sha }} when checking out the hub defaults. job.workflow_sha is not a documented context property; the documented name is github.job_workflow_sha. If this resolves empty, the hub checkout will default to the hub’s default branch instead of the pinned commit.
          ref: ${{ job.workflow_sha }}

.github/workflows/validate-task.yml:53

  • The actionlint ignore regex is overly tied to actionlint’s current rendering of the job context type (the {check_run_id... field list). That makes the gate brittle across actionlint upgrades and also won’t match if the intended context is github.job_workflow_sha (the error would mention job_workflow_sha, not workflow_sha).
        uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0
        with:
          flags: -ignore 'property "workflow_sha" is not defined in object type \{check_run_id'

.github/workflows/build-release-task.yml:201

  • job.workflow_sha is not a documented GitHub Actions context; the documented name for the reusable-workflow SHA is github.job_workflow_sha. As written, this expression is likely to resolve to an empty string at runtime, causing the hub checkout to fall back to the default branch and breaking the “pinned hub defaults” guarantee.
          ref: ${{ job.workflow_sha }}

.github/actions/build-nuget-default/action.yml:70

  • -property:Version is set to $SEMVER2, but SemVer2 can include +buildmetadata (the workflow’s own validate-release logic explicitly strips +...). Passing a +... version through Version can break MSBuild/NuGet version handling or produce inconsistent assembly/package metadata. The prior workflow shape used the assembly version for Version and reserved SemVer2 for PackageVersion.
        dotnet build "$PROJECT_FILE" \
          --configuration "$configuration" \
          -property:OutputPath="$RUNNER_TEMP/publish/" \
          -property:PackageOutputPath="$RUNNER_TEMP/publish/" \
          -property:Version="$SEMVER2" \
          -property:FileVersion="$ASSEMBLY_FILE_VERSION" \
          -property:AssemblyVersion="$ASSEMBLY_VERSION" \
          -property:InformationalVersion="$ASSEMBLY_INFORMATIONAL_VERSION" \
          -property:PackageVersion="$SEMVER2"

Copilot AI review requested due to automatic review settings August 16, 2026 13:59
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the four suppressed findings raised on head 4f6ad84 (round 4), since they carry no thread to reply on directly.

.github/workflows/build-release-task.yml:201 and .github/workflows/build-docker-task.yml:101 ("job.workflow_sha is not a documented context property; the documented name is github.job_workflow_sha. ... the hub checkout will default to the hub's default branch"):
Disproved. This contradicts the round-2 finding on the same lines, which asked for exactly this change. I verified directly against GitHub's own context reference (docs.github.com/en/actions/reference/workflows-and-actions/contexts) before making the change: github.job_workflow_sha does not exist anywhere in that reference. job.workflow_sha does: "The commit SHA of the workflow file that defines the current job," which for a job inside a called reusable workflow is that reusable workflow's own pinned commit, exactly what the hub checkout needs. github.workflow_sha is also real but names the caller's own top-level workflow file instead, the wrong one for this purpose. No change.

.github/workflows/validate-task.yml:53 (the actionlint ignore regex is brittle, tied to the current job context type shape):
Fixed in 1db8c7a. Dropped the in object type {check_run_id... suffix; the ignore now matches only the property name (property "workflow_sha" is not defined), which survives an actionlint schema update. The "wrong context" half of this finding repeats the two disproved above and does not apply.

.github/actions/build-nuget-default/action.yml:70 (-property:Version="$SEMVER2" could carry +buildmetadata and break MSBuild/NuGet version handling):
Declined, matches the live production shape verbatim. This is not something introduced here: LanguageTags, MediaTools, and Utilities all set -property:Version=${{ needs.get-version.outputs.SemVer2 }} in their own live build-release-task.yml today, unchanged. The hub default is authored from that same catalog shape per this stage's brief ("author from today's leaf snippets"). If +buildmetadata in Version is a real defect, it predates this PR and belongs to a separate fix applied fleet-wide to the pattern, not a silent deviation introduced in the one hub default that copies it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (3)

scripts/prose_lint.py:345

  • HUB_HOSTED is sourced from the retire dispositions in spec/divergences.json, but it was only extended for build-release-task.yml. Since this PR also introduces hub-hosted build-docker-task.yml, leaving it out risks prose_lint false-flagging references to that hub-hosted path as missing in downstream contexts (and will diverge from spec/divergences.json once it is updated).
HUB_HOSTED = frozenset({"repo-config/configure.sh", ".github/workflows/build-release-task.yml"})

docs/reusable-workflows.md:296

  • In the "Adopting the Release Chain" publish-release.yml stub example, the publish job calls build-release-task.yml but doesn’t pass the ref input. Elsewhere in this PR (the hub’s own publish-release.yml) the ref is explicitly pinned to github.sha to avoid releasing an unvalidated later commit after a dispatch; the example should reflect that same invariant.
    with:
      branch: ${{ github.ref_name }}
      smoke: false

spec/divergences.json:11

  • spec/divergences.json adds a retire gap for build-release-task.yml but not for build-docker-task.yml, even though this PR also hosts the Docker core in the hub and retires the downstream build-docker-task copies. This will leave downstream build-docker-task.yml copies showing up as UNTRIAGED in divergences reporting instead of as an intentional retire gap.
        { "path": ".github/workflows/build-release-task.yml", "disposition": "retire", "reason": "The release chain is hub-hosted as a workflow_call task, per docs/reusable-workflows.md \"Stage 4: The Release Chain and the Docker Core\", so a downstream copy of this filename is retired rather than re-vendored: the caller stub a repo carries after adoption is publish-release.yml and test-pull-request.yml calling the hub task by pin, and no adopting repo carries a same-named local file. The ten carriers measured on develop at hub 7c67328 are PhotoCleaner, PlexCleaner, LanguageTags, MediaTools, Utilities, aiopurpleair, ESPHome-NonRoot, VSCode-Server-DotNetCore, KiCadLibrary, and homeassistant-purpleair. Delete the copy as each repo adopts the hub task; adoption is a separate, later change per repo (docs/reusable-workflows.md \"Rollout\" Stage 4).", "tracking": null },
        { "path": "LICENSE", "disposition": "accepted", "reason": "Each repo owns its license file. The hub does not standardize license text, so it is intentionally outside the manifest.", "tracking": null },

Copilot AI review requested due to automatic review settings August 16, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.github/actions/build-nuget-default/action.yml:66

  • The NuGet build sets the MSBuild Version property to SemVer2, but this property is typically expected to be the numeric assembly/package base version (and this action already takes assembly-version explicitly). Using SemVer2 can introduce a prerelease suffix (e.g. -alpha) into Version, which can cause inconsistent assembly/package metadata compared to the inputs and the prior workflow snippet behavior.
          -property:Version="$SEMVER2" \

@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the round-6 suppressed finding, a repeat of the round-5 one already answered above.

.github/actions/build-nuget-default/action.yml:66 (-property:Version="$SEMVER2" could introduce a prerelease suffix into Version):
Declined, same evidence as the round-5 answer above. LanguageTags, MediaTools, and Utilities all set -property:Version=${{ needs.get-version.outputs.SemVer2 }} in their own live, unmodified build-release-task.yml today. This hub default is authored from that same catalog shape per this stage's brief. Not a regression introduced here.

ptr727 added a commit that referenced this pull request Aug 16, 2026
Records #762 as the evidence for the Rollout tracker release-chain hub pull request checkbox.
Copilot AI review requested due to automatic review settings August 16, 2026 14:23
@ptr727
ptr727 force-pushed the feature/release-chain-tasks branch from 212e42d to 2e423a5 Compare August 16, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (3)

.github/workflows/publish-release.yml:30

  • WORKFLOW.md defines S8 (“dispatch from a ref other than main or develop”) as fails fast, but this workflow currently turns a feature-branch dispatch into a silent no-op by skipping the publish job via if:. That can make an accidental dispatch look successful while producing no release.
  # Pin to the dispatch-time commit so a push landing after dispatch does not release unvalidated.
  publish:
    name: Publish project release job
    needs: [ validate ]
    if: ${{ github.ref_name == 'main' || github.ref_name == 'develop' }}

.github/actions/build-executable-default/action.yml:86

  • This default action produces release-asset-<branch>-executable, but it names the archive executable.7z. WORKFLOW.md’s console target walkthrough specifies the asset archive name as Console.7z (WORKFLOW.md:284), so this diverges from the documented contract/output.
    - name: Zip build output step
      if: ${{ inputs.smoke != 'true' }}
      shell: bash
      run: 7z a -t7z "$RUNNER_TEMP/executable.7z" "$RUNNER_TEMP"/publish/*

.github/actions/build-nuget-default/action.yml:80

  • When inputs.push is true, NuGet/login is invoked with user: ${{ inputs.nuget-username }}, but there’s no explicit guard that nuget-username is non-empty. If the caller forgets to pass secrets.NUGET_USERNAME, the job will fail with a less actionable error from the action instead of a clear, targeted message.
      if: ${{ inputs.push == 'true' && inputs.smoke != 'true' }}
      id: nuget-login
      uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
      with:
        user: ${{ inputs.nuget-username }}

Copilot AI review requested due to automatic review settings August 16, 2026 14:33
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the three suppressed findings raised on head 2e423a5 (round 7).

.github/workflows/publish-release.yml:30 (a feature-branch dispatch silently no-ops instead of failing fast, per WORKFLOW.md S8/D2.3):
Fixed in fbef62d. Restored a dedicated assert-dispatch-ref job (the exact check the pre-existing hub file had before this PR) ahead of validate and publish, and dropped the job-level if: skip it replaced. A bad ref now fails the run loudly instead of reading as a quiet no-op.

.github/actions/build-executable-default/action.yml:86 (the archive is named executable.7z, diverging from WORKFLOW.md's Console.7z):
Fixed in fbef62d. Renamed to Console.7z, matching WORKFLOW.md's console walkthrough and the retired catalog snippet's own convention (the default targets Console/Console.csproj).

.github/actions/build-nuget-default/action.yml:80 (nuget-username reaching NuGet/login with no empty-value guard):
Fixed in fbef62d. Added a fail-fast step naming the missing secret mapping, mirroring the docker-prepare-default image guard from an earlier round.

Copilot AI review requested due to automatic review settings August 16, 2026 19:03
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the three suppressed findings raised on head b0671fd (round 21), each a direct consequence of that commit's own contract change.

TODO.md:267 and spec/fidelity-model.md:31 (both still describe the publish-release.yml interface contract as requiredJobKeys: ["plan", "publish"], stale after b0671fd added validate):
Accepted, fixed in a1df3a7. Updated both references to match spec/files.json.

catalog/snippets/workflows/README.md:10 (the intro names build-release-task and build-docker-task as hub-hosted reusable tasks a downstream repo reaches rather than carries, but the table carries no rows for either, unlike get-version-task.yml and publish-plan-task.yml named beside them):
Accepted, fixed in a1df3a7. Added a row for each, mirroring the get-version-task.yml/publish-plan-task.yml rows' shape, noting the caller-stub catalog snippet lands with the release that first ships each task (docs/reusable-workflows.md "Rollout," and the design decision that a caller-stub snippet cannot land in this pull request since no release yet carries a pinnable commit for either task).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (6)

spec/files.json:31

  • The publish-release.yml interface contract doesn’t assert that the validate job actually calls validate-task.yml. Without that token requirement, a caller stub could satisfy requiredJobKeys with a no-op validate job and still pass the interface audit, undermining the “publish is gated by the reusable validation task” guarantee.
        { "path": ".github/workflows/publish-release.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["plan", "validate", "publish"], "requireTokensInJob": { "plan": ["publish-plan-task.yml"], "publish": ["build-release-task.yml"] } }, "intentRef": "WORKFLOW.md#d4---release--publish", "appliesTo": ["two-phase", "dispatch-only", "publish-on-merge"] },

spec/audit.py:2159

  • The selftest fixture contract for a publish-release.yml caller stub should mirror the spec/files.json interface contract. If the interface contract requires validate to call validate-task.yml, the selftest’s publish_contract should require that token as well so the fixture tests the intended rule.
        "requiredJobKeys": ["plan", "validate", "publish"],
        "requireTokensInJob": {
            "plan": ["publish-plan-task.yml"],
            "publish": ["build-release-task.yml"],
        },

spec/audit.py:2133

  • This comment block says the validate job has no token requirement, but the release model relies on publish being gated by the reusable validation task. To avoid ambiguity (and keep the selftest prose aligned with the interface contract), it’s better to state that validate is expected to call validate-task.yml and the contract asserts it.
    # The publish-release.yml caller stub once the release chain is hub-hosted, plan, validate and publish job keys.
    # Each names its hub task by token, per docs/reusable-workflows.md "Adopting the Release Chain".
    # Validate carries no token requirement of its own.
    # Its presence as a job key is what stops a stub from skipping the gate, per WORKFLOW.md's source-only shape.

docs/reusable-workflows.md:450

  • The example caller stub uses secrets: inherit when calling validate-task.yml, but this document’s “Secrets and Permissions” section states callers should map secrets explicitly (and that secrets: inherit isn’t used for hub tasks). Prefer explicitly mapping the single declared secret (CODECOV_TOKEN) or omitting secrets entirely, so the example matches the documented contract.
    name: Validate job
    needs: [plan]
    if: ${{ needs.plan.outputs.publish == 'true' }}
    uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@<sha> # <tag>
    secrets: inherit

.github/workflows/build-release-task.yml:120

  • This workflow inherits whatever GITHUB_TOKEN permissions the caller grants to the job that invokes it, so jobs like get-version/build-executable/build-pypi/docker-* will run with broader scopes (e.g. contents: write, actions: write) than they need. Setting a workflow-level default permissions: { contents: read } keeps those jobs least-privilege while preserving job-level overrides for build-nuget (id-token) and github-release (contents/actions write).
    secrets:
      # The nuget.org profile name for OIDC trusted publishing.
      # Not needed when enable_nuget is false.
      NUGET_USERNAME:
        required: false
      # Not needed when enable_docker is false.
      DOCKER_HUB_USERNAME:
        required: false
      DOCKER_HUB_ACCESS_TOKEN:
        required: false

jobs:

  get-version:

.github/workflows/build-docker-task.yml:86

  • Like build-release-task.yml, this workflow will run with the caller job’s GITHUB_TOKEN scopes unless it sets its own default. Adding a workflow-level permissions: { contents: read } keeps docker-prepare/build-docker least-privilege even when the caller grants broader permissions for other jobs.
    secrets:
      # Not needed on a smoke build, but declared required since a real caller always logs in for cache reads.
      DOCKER_HUB_USERNAME:
        required: true
      DOCKER_HUB_ACCESS_TOKEN:
        required: true

jobs:

Round-22's on-head suppressed findings, on head a1df3a7:

- spec/files.json: the publish-release.yml interface contract required
  a validate job key but never checked it actually calls
  validate-task.yml, so a no-op validate job would still pass. Added
  requireTokensInJob for validate, mirroring test-pull-request.yml's
  own existing contract shape.
- spec/audit.py: updated the check_interface selftest's publish
  contract and its explanatory comment to match, and added a case for
  a validate job that never names validate-task.yml.
- docs/reusable-workflows.md: the new publish-release.yml stub example
  called validate-task.yml with secrets: inherit, contradicting this
  same doc's own "Secrets and Permissions" section (secrets: inherit
  is not used on the fleet's personal account). Mapped the one secret
  validate-task.yml declares, CODECOV_TOKEN, explicitly instead.

Declined two suppressed findings from the same round, no change:
build-release-task.yml and build-docker-task.yml adding a
workflow-level default permissions: contents: read. No existing hub
task (get-version-task.yml, publish-plan-task.yml, validate-task.yml,
merge-bot-task.yml) sets a workflow-level permissions default, relying
instead on the documented per-job elevation only where a job genuinely
writes. Adding one here would be new precedent, not a fix, so it is
answered in the PR conversation rather than applied.
Copilot AI review requested due to automatic review settings August 16, 2026 19:17
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the six suppressed findings raised on head a1df3a7 (round 22).

spec/files.json:31 (the validate job key requirement added in b0671fd never checks the job actually calls validate-task.yml, so a no-op validate job would still pass):
Accepted, fixed in 003c444. Added requireTokensInJob for validate, mirroring test-pull-request.yml's own existing contract shape ("validate": ["validate-task.yml"]).

spec/audit.py:2159 (the selftest's publish_contract fixture should mirror the interface contract's new token requirement):
Accepted, fixed in 003c444. Added the same token to the selftest contract and a new case for a validate job that never names validate-task.yml.

spec/audit.py:2133 (the selftest's own comment said validate carries no token requirement, which is no longer true once the interface contract requires one):
Accepted, fixed in 003c444. Reworded.

docs/reusable-workflows.md:450 (the new publish-release.yml stub example calls validate-task.yml with secrets: inherit, contradicting this same doc's own "Secrets and Permissions" section, which states secrets: inherit is not used on the fleet's personal account):
Accepted, fixed in 003c444. Mapped the one secret validate-task.yml declares, CODECOV_TOKEN, explicitly instead. (A pre-existing secrets: inherit at line 353, in the test-pull-request.yml smoke-build example from an earlier, already-merged stage, is the same defect but outside this PR's diff. Not touched here, flagged for a follow-up.)

.github/workflows/build-release-task.yml:120 and .github/workflows/build-docker-task.yml:86 (add a workflow-level default permissions: { contents: read } so a callee job with no job-level permissions: does not inherit the caller's broader grant):
Declined. No existing hub task sets a workflow-level permissions: default: get-version-task.yml, publish-plan-task.yml, validate-task.yml, and merge-bot-task.yml all rely solely on the documented per-job elevation ("a hub task declares no job-level permissions: where every write goes through the App token," docs/reusable-workflows.md "Secrets and Permissions"), leaving the workflow-level default to whatever the caller grants. Adding one here would be new precedent for these two tasks alone, not a fix to a regression, and picking it up fleet-wide is a separate design decision for a later stage rather than a silent addition in this one.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/build-docker-task.yml:79

  • The comment above DOCKER_HUB_* secrets says they are “Not needed on a smoke build”, but the workflow always runs the Docker Hub login step (including smoke) so these secrets are still required. This is misleading for callers and conflicts with the job behavior.
      # Not needed on a smoke build, but declared required since a real caller always logs in for cache reads.

spec/files.json:31

  • PR description says the publish-release.yml interface contract is requiredJobKeys: ["plan", "publish"], but the code enforces ["plan", "validate", "publish"]. Please update the PR description to match the implemented contract (or adjust the contract if the description is the intended requirement).
        { "path": ".github/workflows/publish-release.yml", "fidelity": "interface", "contract": { "requiredJobKeys": ["plan", "validate", "publish"], "requireTokensInJob": { "plan": ["publish-plan-task.yml"], "validate": ["validate-task.yml"], "publish": ["build-release-task.yml"] } }, "intentRef": "WORKFLOW.md#d4---release--publish", "appliesTo": ["two-phase", "dispatch-only", "publish-on-merge"] },

…ain-tasks

# Conflicts:
#	.agents/skills/operational-vs-release-workflow/references/release-publish-mechanics.md
#	.claude-plugin/fleet-skills/.source-digest
#	.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/release-publish-mechanics.md
#	.github/actionlint.yaml
#	WORKFLOW.md
#	catalog/snippets/workflows/README.md
#	docs/reusable-workflows.md
#	spec/files.json
Copilot AI review requested due to automatic review settings August 16, 2026 19:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Round-23's suppressed finding on head 003c444, still present after the develop merge:

.github/workflows/build-docker-task.yml declared DOCKER_HUB_USERNAME and
DOCKER_HUB_ACCESS_TOKEN required with a comment saying they are
"not needed on a smoke build," but the login step a few lines down
runs unconditionally, even on smoke, for the higher Docker Hub rate
limit on cache reads, and a fork PR without both secrets cannot run
the Docker smoke build at all. Reworded to state what is actually
true.

Also updated the PR description, whose text still quoted spec/files.json's
publish-release.yml contract as requiredJobKeys: [plan, publish], stale
after b0671fd and 003c444 added validate.
Copilot AI review requested due to automatic review settings August 16, 2026 19:34
@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the suppressed finding raised on head 003c444 (round 23), which the develop merge (e7f03e2) did not touch and remained real on the new head even though the round-24 full-coverage review did not re-flag it.

.github/workflows/build-docker-task.yml:79 (the comment says DOCKER_HUB_USERNAME/DOCKER_HUB_ACCESS_TOKEN are "not needed on a smoke build," but the login step below runs unconditionally):
Accepted, fixed in a4c5ff9. The comment was wrong: the login step's own comment a few lines down already says "Always login, even for smoke, for the higher Docker rate limits," and a fork PR carrying neither secret cannot run the Docker smoke build at all. Reworded.

Also fixed in the same commit, from the same round: the PR description still quoted spec/files.json's publish-release.yml contract as requiredJobKeys: ["plan", "publish"], stale after b0671fd and 003c444 added validate. Updated the description to match.

Current head a4c5ff9: review_on_head=yes coverage=full unresolved=0 suppressed on_head=0 checks=7/7 merge=CLEAN as of the round before this push. Re-requesting review on this head.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/actions/build-nuget-default/action.yml:70

  • In the NuGet default hook, dotnet build sets -property:Version to SemVer2. That value may include prerelease/build metadata, and it also differs from the established pattern in this repo (e.g., executable builds use AssemblyVersion for Version while reserving SemVer2 for PackageVersion). Setting Version to AssemblyVersion avoids accidentally threading prerelease metadata into the assembly/package version surface.
        dotnet build "$PROJECT_FILE" \
          --configuration "$configuration" \
          -property:OutputPath="$RUNNER_TEMP/publish/" \
          -property:PackageOutputPath="$RUNNER_TEMP/publish/" \
          -property:Version="$SEMVER2" \
          -property:FileVersion="$ASSEMBLY_FILE_VERSION" \
          -property:AssemblyVersion="$ASSEMBLY_VERSION" \
          -property:InformationalVersion="$ASSEMBLY_INFORMATIONAL_VERSION" \
          -property:PackageVersion="$SEMVER2"

.github/workflows/build-release-task.yml:238

  • build-nuget requests id-token: write at the job level even when it’s only doing a smoke/validation build (the actual push is gated later via push: ${{ inputs.nuget && !inputs.smoke }}). This means any PR smoke build that enables NuGet compilation must grant OIDC (id-token: write) to the reusable workflow call even though no publish occurs, which expands the OIDC attack surface for non-publishing runs.

If the intent is least-privilege for smoke builds, consider splitting NuGet into two phases: a build job (no id-token) and a publish job (only runs when inputs.nuget && !inputs.smoke, and is the only job that requests id-token: write). That likely requires adjusting the hook/default so the build phase never performs the push itself.

    # OIDC trusted publishing through NuGet/login needs id-token: write, granted by the caller on its call to this task.
    # It is declared alongside contents: read, since a job-level permissions: block zeroes every unlisted scope, and this job's checkout steps need it.
    permissions:
      contents: read
      id-token: write

@ptr727

ptr727 commented Aug 16, 2026

Copy link
Copy Markdown
Owner Author

Answering the two suppressed findings raised on head a4c5ff9 (round 25).

.github/actions/build-nuget-default/action.yml:70 (-property:Version="$SEMVER2" could carry prerelease/build metadata):
Declined, same evidence as rounds 5, 6, 11, 12, 13, 16, 17. LanguageTags, MediaTools, and Utilities all set -property:Version=${{ needs.get-version.outputs.SemVer2 }} in their own live, unmodified build-release-task.yml today. This hub default is authored from that same catalog shape per this stage's brief. Not a regression introduced here.

.github/workflows/build-release-task.yml:238 (build-nuget grants id-token: write at the job level even on a smoke build, since the job combines build and optional publish, widening the OIDC attack surface for a run that never publishes):
Declined. The grant is inert on smoke. NuGet/login, the only step that consumes id-token: write (it is what exchanges the token for an OIDC identity), runs if: inputs.push == 'true' && inputs.smoke != 'true' in the hub default hook, and push is derived by the caller as inputs.nuget && !inputs.smoke, so push is always false on a smoke build. The permission is present on the job's GITHUB_TOKEN but no step ever calls for the OIDC token it would carry, so there is no live credential a smoke run can use. Declaring the scope at this job's header, rather than splitting build and publish into two jobs, is also the documented shape: docs/reusable-workflows.md "Secrets and Permissions" states a task whose job genuinely writes with GITHUB_TOKEN "declares that scope in the callee job," at the one entry point, which this task's own header comment (OIDC trusted publishing through NuGet/login needs id-token: write, granted by the caller on its call to this task) already follows. Splitting the job to shave an unused, unexercisable grant is a larger structural change than this finding's stated risk justifies.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.github/workflows/build-release-task.yml:149

  • The inlined NBGV step doesn’t set IGNORE_GITHUB_REF: "true" like .github/workflows/get-version-task.yml does. Without it, NBGV can classify versions based on the caller run’s GITHUB_REF instead of the commit checked out via inputs.ref, which breaks the intended “caller may build another branch/commit” behavior (and can trip validate-release on a develop leg built from a main-triggered run). Align this with get-version-task.yml by setting the env var on the dotnet/nbgv@master step.
      # The nbgv action is floated on @master, because its tag stream lags master.
      # Dependabot tag-tracking would otherwise propose a downgrade.
      - name: Run Nerdbank.GitVersioning tool step
        id: nbgv
        uses: dotnet/nbgv@master

@ptr727
ptr727 merged commit 9874122 into develop Aug 16, 2026
9 checks passed
@ptr727
ptr727 deleted the feature/release-chain-tasks branch August 16, 2026 20:55
ptr727 added a commit that referenced this pull request Aug 16, 2026
… Default (#768)

Promote `develop` to `main`, carrying the remaining stages of the
hub-hosted reusable-workflow rollout and the changes that landed beside
them:

- #759 Host Get-Version and Publish-Plan as Hub Reusable Tasks
- #760 Host the Validate Task and Reshape the Test Pull Request Stub
(settles #729 by design: the hub's validate task runs `uvx
<tool>@latest`, since Dependabot tracks the action pins and not a uvx
version)
- #761 Host the Type-Specific Tasks and Retire the Date Badge
- #762 Host the Release Chain and the Docker Core in the Hub
- #748 and #752, the staged rollout tracker and the PhotoCleaner
merge-bot pilot record
- #758 Flip the Fleet Line-Ending Default from CRLF to LF
- #753, #755, #756, #764, host-setup and test-collection changes

The release that follows this promotion is the first tag carrying every
hub task, so it is the pin the stage 2 to 5 adoptions and their catalog
snippets use. It is also the first run of the hub's own
`publish-release.yml` through `build-release-task.yml` with every target
disabled, which is the live proof that `github-release` runs when its
build needs are skipped.

Closes #729.
Refs #521 (hub half shipped, the merge-bot adoption sweep is what
remains).
ptr727 added a commit that referenced this pull request Aug 16, 2026
## What

`build-release-task.yml` no longer declares job-level `permissions:` on
`build-nuget` (`id-token: write`) or `github-release` (`contents:
write`, `actions: write`). The caller grants what the targets it enables
need, and `docs/reusable-workflows.md` "Adopting the Release Chain" says
so.

## Why

The hub's first release through the task, dispatched on `main` at
`82fecef` after #768, ended in `startup_failure` (run 31972504539). A
called job's `permissions:` block is validated against the caller's
grant before its `if:` runs (GOVERNANCE.md "Workflow YAML Conventions",
the reusable-workflows bullet), so `build-nuget` asking for `id-token:
write` fails any caller that does not grant it, the hub's own `publish`
job included, even though the job is disabled. `github-release`'s block
has the same shape and would fail every smoke build, since a Dependabot
pull request holds a read-only token that cannot grant `contents:
write`. This is the finding #762 declined on the ground that only
`NuGet/login` consumes the scope: the consumption was never the problem,
the declaration was.

## Verification

actionlint, prose gate, audit self-test, 686 unit tests, markdownlint.
The live proof is the re-dispatched hub release after this promotes,
which is the next step.
ptr727 added a commit that referenced this pull request Aug 16, 2026
…nippets (#775)

Release `2.0.352` (`main` `0b07a59d7c65d07d8df275a96deaf2e06cbefd51`) is
the
first tag carrying every hub-hosted reusable workflow task, produced by
[the publish-release
run](https://github.com/ptr727/ProjectTemplate/actions/runs/31974067182)
after promotion #774. In that run `build-release-task.yml` ran with
every
target disabled and its `github-release` job succeeded while all five
build
jobs skipped, proving the `github-release` skip fix (#762) and the
caller-granted scopes (#772). The earlier release attempt on `82fecef`

([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972504539))
ended in `startup_failure` because `build-nuget`/`github-release`
declared
job-level permissions; #772 fixed it.

## docs/reusable-workflows.md "Rollout"

- Ticks the two stage-0 merge-bot proof items: Dependabot PR #771 merged
to
  `develop` with `--squash` through `merge-bot-task.yml`

([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972622149)),
  and Dependabot PR #770 merged to `main` with `--merge`

([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972611554)).
- Ticks "Promoted and released" in stages 2, 3, 4, and 5 with `2.0.352`
on
  `0b07a59d` and #774, and stage 4 also notes the first attempt's
  `startup_failure` and its #772 fix.
- Leaves every adoption and pilot checkbox untouched.
- Does **not** tick either stage's "Catalog snippets ... pinned to that
release" item, since neither stage's full set of snippets is complete
this
  time: stage 2's release-with-smoke shape still calls its own repo's
`build-release-task.yml` by `./` path rather than the hub's, and stage
5's
  `deploy-site.yml`, `publish-docker-readme-task.yml`, and
  `check-upstream-version-task.yml` each stay open for reasons the doc
  itself already states (no manifest-wide snippet by design, or a job
  embedded in a larger stub rather than a standalone caller). Both items
  gain an inline note identifying which of their snippets this PR adds.

## New catalog snippets

Three of the five/six candidate files, each pinned
`0b07a59d7c65d07d8df275a96deaf2e06cbefd51 # 2.0.352` and byte-identical
to
its doc stub block apart from the pin (and, where the doc's own comment
wrapping does not survive an actual YAML file's comment-wrap rule, a
reworded comment kept in sync on both sides):

- `catalog/snippets/workflows/test-pull-request.yml` (the no-build gate
  shape from "Adopting the Gates")
- `catalog/snippets/workflows/publish-release.yml` (the release-repo
shape
  from "Adopting the Release Chain")
- `catalog/snippets/workflows/run-periodic-codegen-pull-request.yml`
(the
  codegen stub, which the doc already says keeps today's per-repo shape)

Skipped, with the doc updated to say why:

- `test-pull-request-smoke.yml` - the doc's smoke stub still calls the
caller's own `./.github/workflows/build-release-task.yml` by local path,
  never the hub's `build-release-task.yml` directly, so it does not meet
  the "calls the hub task" condition.
- `deploy-site.yml` - the doc's own "Deploy-site" paragraph states it
  "has no manifest-wide catalog snippet either," the same reason
`get-version-task.yml`/`publish-plan-task.yml` have none: each site's
own
  shape varies too much around the shared `deploy` job.
- `check-upstream-version.yml` - the doc gives only the
`check-upstream-version:`
job body, not a complete top-level trigger/concurrency shape, and unlike
the codegen stub there is no doc statement that a uniform shape exists
to
  copy.

`catalog/snippets/workflows/README.md` gained rows for the three new
snippets and an updated intro paragraph explaining the partial rollout.

## TODO.md

Added one **Settled** line under "Host the type-specific tasks" naming
`2.0.352` as the release that makes stages 2-5 adoptable and the run
that
proved the release chain, and noting #769 as the one open follow-up from
the promotion review.

## reports/workflow-reuse.md

Regenerated with `python3 spec/workflow_reuse.py --report` against the
live
fleet at hub `63d244b` (this branch's base). The numbers move against
the
last committed report only because the live fleet moved since then, not
because of anything in this PR.

## Gates run locally

`spec/validate.py`, `python3 -m unittest discover -s scripts/tests` (686
tests), `spec/audit.py --selftest`, `spec/workflow_reuse.py --selftest`,
`scripts/repo_gate.py` (sha-pin resolved against GitHub),
`scripts/build_dist.py --check`,
`scripts/prose_lint.py` (all nine checks), `ruff check`/`ruff format
--check`,
`mypy`, and the Docker `actionlint`, `markdownlint-cli2`, and
`editorconfig-checker` linters (only `.ruff_cache` noise there).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants