Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Their CI is lint/validation only (editorconfig/EOL plus domain linters such as H
### Two Layers: Orchestration vs Build

- **Orchestration** is generic and forms the standardization baseline **at the job level**: the single-branch publisher, the `get-version`, `validate-release`, and `github-release` jobs, and the `changes -> smoke-build -> aggregator` shape of the PR workflow. These job *bodies* should not need per-repo edits.
- **Build** is repo-owned in shape: the leaf a target runs. A repo owning its release task hosts its leaves itself. A repo calling the hub-hosted task shapes a leaf through a composite-action hook of its own, which the task runs in place of its hub default where that file exists. For the .NET, NuGet, and PyPI leaves it is `.github/actions/<job>/action.yml`, named for the build job (`dotnet-publish`, `build-nuget`, `build-pypi`). For the Docker leaf it is `.github/actions/docker-prepare/action.yml`, which a `docker_matrix` the caller passes bypasses along with the default. A `docker-build-base` hook, which has no default, builds a shared base layer where the caller sets `docker_build_base`.
- **What the repo curates** (by design, not a leak): the *list* of targets. A repo calling the hub-hosted release task reaches it by pin and carries no copy, so adding or dropping a target edits the caller's own surface. That is the `enable_<target>` value its publisher passes, the publisher's `on.push.paths` entries where it carries a `push` trigger, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3). It is also the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. The release task's `enable_*` inputs, its per-target build jobs, and their `github-release` and `build-docker` `needs:` entries belong to the task, so a target the hub task declares no input for is a change to that task first (`WORKFLOW.md` section 6, the `library` row). A repo owning its release task edits those in its own file, where "verbatim" applies to the `github-release` job and the version/publish-plan logic, except that job's own `needs:` list, and never to the task's job list.
- **Build** is repo-owned in shape: the leaf a target runs. A repo owning its release task hosts its leaves itself. A repo calling the hub-hosted task shapes a leaf through a composite-action hook of its own, which the task runs in place of its hub default where that file exists. For the .NET, NuGet, and PyPI leaves it is `.github/actions/<job>/action.yml`, named for the build job (`dotnet-publish`, `build-nuget`, `build-pypi`). For the Docker leaf it is `.github/actions/docker-prepare/action.yml`, which a `docker_matrix` the caller passes bypasses along with the default. A `docker-build-base` hook, which has no default, builds a shared base layer where the caller sets `docker_build_base`. A file target the task ships no leaf for, such as a data zip, is the `build-release-asset` hook, which has no default either and runs where the caller sets `enable_release_asset`. It writes the target's files into the directory the task names, on a smoke run too, so the pull request proves they build, and the task uploads them.
- **What the repo curates** (by design, not a leak): the *list* of targets. A repo calling the hub-hosted release task reaches it by pin and carries no copy, so adding or dropping a target edits the caller's own surface. That is the `enable_<target>` value its publisher passes, the publisher's `on.push.paths` entries where it carries a `push` trigger, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3). It is also the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. The release task's `enable_*` inputs, its per-target build jobs, and their `github-release` and `build-docker` `needs:` entries belong to the task, so a target the hub task declares no input for is a change to that task first (`WORKFLOW.md` section 6, the `library` row), except a file target, which the `build-release-asset` hook carries. A repo owning its release task edits those in its own file, where "verbatim" applies to the `github-release` job and the version/publish-plan logic, except that job's own `needs:` list, and never to the task's job list.

### The Seam Contract

Expand Down Expand Up @@ -101,7 +101,7 @@ flowchart TD

Pick each output's path by **where the artifact goes**:

- **File on the GitHub release** (zip, binary, packaged library): one leaf per output uploading `release-asset-<branch>-<name>`. The repo keeps `expect_release_assets: true` (its default).
- **File on the GitHub release** (zip, binary, packaged library): one leaf per output uploading `release-asset-<branch>-<name>`, or, for a repo calling the hub-hosted task, the files its `build-release-asset` hook writes, which the task uploads as one such artifact. The repo keeps `expect_release_assets: true` (its default).
- **Package-registry push** (NuGet, PyPI): the leaf builds and uploads a build artifact (`nuget-build-<branch>` / `pypi-build-<branch>`), and a separate `publish-<target>` job in the **publishing repository's own** publisher consumes it and pushes. Both registries publish through OIDC Trusted Publishing, never a stored API key, and two things put that push outside the leaf. Trusted publishing validates the OIDC token's `job_workflow_ref` claim, which names the workflow the job actually ran from, so a push made from a reusable workflow a *different* repository hosts is rejected at the token exchange, NuGet.org answering `HTTP 401` with `does not start with <owner>/<repo>/.github/workflows/`. That alone rules out a leaf another repository hosts. A leaf this repository hosts clears the claim, and the split still applies to it, because a called job declaring no `permissions:` runs under the calling job's whole grant, so a push anywhere inside the release task would put `id-token: write` on every job in it rather than at the one entry point D7.2 requires. The registered trusted-publishing policy therefore names the publisher, `publish-release.yml`. PyPI additionally gates its publish job behind an environment. NuGet.org binds its policy to the workflow file rather than to an environment and needs none. NuGet's leaf also uploads a `release-asset-*` carrying the package, and PyPI contributes none.
- **Image-registry push** (Docker): the leaf pushes the default branch multi-arch (amd64+arm64) and any other branch `amd64`-only (arm64 emulation is reserved for the released image), and contributes no `release-asset-*`.
- **Filesystem on a host the project owns** (a static site, a config tree): the leaf builds the tree, ships it to the host, and contributes no `release-asset-*`. The transport is the repo's own. What the contract fixes is that the deploy is a **separate `workflow_dispatch`** from the release, so a redeploy of an unchanged commit mints no tag and a host rebuild, a rollback, or proving a branch on a non-production environment costs nothing; that its credentials come from a **per-environment GitHub Environment** rather than the repository secret store; and that the deploy ends by asserting **what the host serves** rather than the transport's exit status (D4.6). Retention at the destination is bounded by a declared count with one side recorded as owning the prune, which is the deploy where its credential can observe the destination and the host where that credential is deliberately write-only (D5.6).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output

### D6 - Seam / Architecture Conformance

- **D6.1 Pattern handoff.** Output: the release job downloads by `pattern:`/`merge-multiple:`, not `artifact-ids:`. **File** targets upload `release-asset-<branch>-<target>`, and a target contributing no file to the release (Docker, PyPI) uploads no `release-asset-*` of its own, per D4.3, whatever other transfer artifact it uploads. The `pattern:` download is canonical for a single-target repo too, which does not special-case itself to `artifact-ids:`.
- **D6.1 Pattern handoff.** Output: the release job downloads by `pattern:`/`merge-multiple:`, not `artifact-ids:`. **File** targets upload `release-asset-<branch>-<target>`, where a file target the hub-hosted task builds through the `build-release-asset` hook fills `<target>` with `build-release-asset`, and a target contributing no file to the release (Docker, PyPI) uploads no `release-asset-*` of its own, per D4.3, whatever other transfer artifact it uploads. The `pattern:` download is canonical for a single-target repo too, which does not special-case itself to `artifact-ids:`.
- **D6.2 Branch drives config.** Output: a called workflow's branch-derived config reads `inputs.branch`, never `github.ref_name`.
- **D6.3 Branch-named artifacts.** Output: every artifact name carries the branch, so a branch's artifacts do not collide with another branch's.
- **D6.4 Target add/drop is consistent.** Output: adding or dropping a target updates **all** of: the `enable_<target>` value the publisher passes to the release task, the publisher's `on.push.paths` entries where it carries a `push` trigger, the `changes` paths-filter entry + output, the `smoke-build` enable-forward, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3), plus, for a package target, the separate `publish-<target>` job. A repo owning its release task also updates the target's build job there, `build-<target>` or `dotnet-publish` for the .NET target, and its `github-release` and `build-docker` `needs:` entries. Everything in the `github-release` job **except its `needs:` list** stays verbatim, and so does the version and publish-plan logic. "Verbatim" never reaches the surfaces this item requires editing, that `needs:` list, the release task's job list, and the paths-filter among them. *Prevents: a partial subset that startup-fails on a missing leaf or never smoke-builds a target.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
31837ec0ea2c7e2b
105265bdb5c8abd9
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Their CI is lint/validation only (editorconfig/EOL plus domain linters such as H
### Two Layers: Orchestration vs Build

- **Orchestration** is generic and forms the standardization baseline **at the job level**: the single-branch publisher, the `get-version`, `validate-release`, and `github-release` jobs, and the `changes -> smoke-build -> aggregator` shape of the PR workflow. These job *bodies* should not need per-repo edits.
- **Build** is repo-owned in shape: the leaf a target runs. A repo owning its release task hosts its leaves itself. A repo calling the hub-hosted task shapes a leaf through a composite-action hook of its own, which the task runs in place of its hub default where that file exists. For the .NET, NuGet, and PyPI leaves it is `.github/actions/<job>/action.yml`, named for the build job (`dotnet-publish`, `build-nuget`, `build-pypi`). For the Docker leaf it is `.github/actions/docker-prepare/action.yml`, which a `docker_matrix` the caller passes bypasses along with the default. A `docker-build-base` hook, which has no default, builds a shared base layer where the caller sets `docker_build_base`.
- **What the repo curates** (by design, not a leak): the *list* of targets. A repo calling the hub-hosted release task reaches it by pin and carries no copy, so adding or dropping a target edits the caller's own surface. That is the `enable_<target>` value its publisher passes, the publisher's `on.push.paths` entries where it carries a `push` trigger, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3). It is also the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. The release task's `enable_*` inputs, its per-target build jobs, and their `github-release` and `build-docker` `needs:` entries belong to the task, so a target the hub task declares no input for is a change to that task first (`WORKFLOW.md` section 6, the `library` row). A repo owning its release task edits those in its own file, where "verbatim" applies to the `github-release` job and the version/publish-plan logic, except that job's own `needs:` list, and never to the task's job list.
- **Build** is repo-owned in shape: the leaf a target runs. A repo owning its release task hosts its leaves itself. A repo calling the hub-hosted task shapes a leaf through a composite-action hook of its own, which the task runs in place of its hub default where that file exists. For the .NET, NuGet, and PyPI leaves it is `.github/actions/<job>/action.yml`, named for the build job (`dotnet-publish`, `build-nuget`, `build-pypi`). For the Docker leaf it is `.github/actions/docker-prepare/action.yml`, which a `docker_matrix` the caller passes bypasses along with the default. A `docker-build-base` hook, which has no default, builds a shared base layer where the caller sets `docker_build_base`. A file target the task ships no leaf for, such as a data zip, is the `build-release-asset` hook, which has no default either and runs where the caller sets `enable_release_asset`. It writes the target's files into the directory the task names, on a smoke run too, so the pull request proves they build, and the task uploads them.
- **What the repo curates** (by design, not a leak): the *list* of targets. A repo calling the hub-hosted release task reaches it by pin and carries no copy, so adding or dropping a target edits the caller's own surface. That is the `enable_<target>` value its publisher passes, the publisher's `on.push.paths` entries where it carries a `push` trigger, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3). It is also the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. The release task's `enable_*` inputs, its per-target build jobs, and their `github-release` and `build-docker` `needs:` entries belong to the task, so a target the hub task declares no input for is a change to that task first (`WORKFLOW.md` section 6, the `library` row), except a file target, which the `build-release-asset` hook carries. A repo owning its release task edits those in its own file, where "verbatim" applies to the `github-release` job and the version/publish-plan logic, except that job's own `needs:` list, and never to the task's job list.

### The Seam Contract

Expand Down Expand Up @@ -101,7 +101,7 @@ flowchart TD

Pick each output's path by **where the artifact goes**:

- **File on the GitHub release** (zip, binary, packaged library): one leaf per output uploading `release-asset-<branch>-<name>`. The repo keeps `expect_release_assets: true` (its default).
- **File on the GitHub release** (zip, binary, packaged library): one leaf per output uploading `release-asset-<branch>-<name>`, or, for a repo calling the hub-hosted task, the files its `build-release-asset` hook writes, which the task uploads as one such artifact. The repo keeps `expect_release_assets: true` (its default).
- **Package-registry push** (NuGet, PyPI): the leaf builds and uploads a build artifact (`nuget-build-<branch>` / `pypi-build-<branch>`), and a separate `publish-<target>` job in the **publishing repository's own** publisher consumes it and pushes. Both registries publish through OIDC Trusted Publishing, never a stored API key, and two things put that push outside the leaf. Trusted publishing validates the OIDC token's `job_workflow_ref` claim, which names the workflow the job actually ran from, so a push made from a reusable workflow a *different* repository hosts is rejected at the token exchange, NuGet.org answering `HTTP 401` with `does not start with <owner>/<repo>/.github/workflows/`. That alone rules out a leaf another repository hosts. A leaf this repository hosts clears the claim, and the split still applies to it, because a called job declaring no `permissions:` runs under the calling job's whole grant, so a push anywhere inside the release task would put `id-token: write` on every job in it rather than at the one entry point D7.2 requires. The registered trusted-publishing policy therefore names the publisher, `publish-release.yml`. PyPI additionally gates its publish job behind an environment. NuGet.org binds its policy to the workflow file rather than to an environment and needs none. NuGet's leaf also uploads a `release-asset-*` carrying the package, and PyPI contributes none.
- **Image-registry push** (Docker): the leaf pushes the default branch multi-arch (amd64+arm64) and any other branch `amd64`-only (arm64 emulation is reserved for the released image), and contributes no `release-asset-*`.
- **Filesystem on a host the project owns** (a static site, a config tree): the leaf builds the tree, ships it to the host, and contributes no `release-asset-*`. The transport is the repo's own. What the contract fixes is that the deploy is a **separate `workflow_dispatch`** from the release, so a redeploy of an unchanged commit mints no tag and a host rebuild, a rollback, or proving a branch on a non-production environment costs nothing; that its credentials come from a **per-environment GitHub Environment** rather than the repository secret store; and that the deploy ends by asserting **what the host serves** rather than the transport's exit status (D4.6). Retention at the destination is bounded by a declared count with one side recorded as owning the prune, which is the deploy where its credential can observe the destination and the host where that credential is deliberately write-only (D5.6).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output

### D6 - Seam / Architecture Conformance

- **D6.1 Pattern handoff.** Output: the release job downloads by `pattern:`/`merge-multiple:`, not `artifact-ids:`. **File** targets upload `release-asset-<branch>-<target>`, and a target contributing no file to the release (Docker, PyPI) uploads no `release-asset-*` of its own, per D4.3, whatever other transfer artifact it uploads. The `pattern:` download is canonical for a single-target repo too, which does not special-case itself to `artifact-ids:`.
- **D6.1 Pattern handoff.** Output: the release job downloads by `pattern:`/`merge-multiple:`, not `artifact-ids:`. **File** targets upload `release-asset-<branch>-<target>`, where a file target the hub-hosted task builds through the `build-release-asset` hook fills `<target>` with `build-release-asset`, and a target contributing no file to the release (Docker, PyPI) uploads no `release-asset-*` of its own, per D4.3, whatever other transfer artifact it uploads. The `pattern:` download is canonical for a single-target repo too, which does not special-case itself to `artifact-ids:`.
- **D6.2 Branch drives config.** Output: a called workflow's branch-derived config reads `inputs.branch`, never `github.ref_name`.
- **D6.3 Branch-named artifacts.** Output: every artifact name carries the branch, so a branch's artifacts do not collide with another branch's.
- **D6.4 Target add/drop is consistent.** Output: adding or dropping a target updates **all** of: the `enable_<target>` value the publisher passes to the release task, the publisher's `on.push.paths` entries where it carries a `push` trigger, the `changes` paths-filter entry + output, the `smoke-build` enable-forward, and `expect_release_assets` where the change adds the first file target or drops the last (D4.3), plus, for a package target, the separate `publish-<target>` job. A repo owning its release task also updates the target's build job there, `build-<target>` or `dotnet-publish` for the .NET target, and its `github-release` and `build-docker` `needs:` entries. Everything in the `github-release` job **except its `needs:` list** stays verbatim, and so does the version and publish-plan logic. "Verbatim" never reaches the surfaces this item requires editing, that `needs:` list, the release task's job list, and the paths-filter among them. *Prevents: a partial subset that startup-fails on a missing leaf or never smoke-builds a target.*
Expand Down
Loading
Loading