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 @@ -29,7 +29,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
- **D3.1 One branch per run.** Input: a publish triggered on `main` or `develop`. Output: the run builds and versions that one branch, and `github.ref` names it, so NBGV classifies it directly (no `IGNORE_GITHUB_REF`). *Prevents: a cross-branch ref mismatch misclassifying the version.*
- **D3.2 Default = public, others = prerelease.** Output: default branch -> `X.Y.Z`, and any other -> `X.Y.Z-g<sha>`. The default-branch literal in the gate, the `prerelease` expression, and `version.json` MUST all name the repo's real default branch.
- **D3.3 Version floor + git height.** Output: `version.json` sets the major.minor floor. NBGV appends the git height as the patch, bumped only for a functional change by the maintainer. NBGV and `version.json` are retained even by a no-compiler repo (they own the tag).
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). The develop `.dev0` build must remain `pip install --pre`-selectable and sort above the default release (NBGV git height in the release segment keeps develop ahead). *Prevents: a non-default leg published as a release, and a renamed/extra branch silently getting a plain version.*
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). *Prevents: a non-default leg published as a release.*
- **D3.5 Wrapper repos may use an external version.** Output: a repo wrapping an upstream release drives its build/image version from a committed `name -> version` state file, while NBGV still tags the release. *Note: the tracker (the writer) ships without consumer wiring, so a wrapper must wire the leaf to read the state file (e.g. `jq` into the image tag) instead of `SemVer2`. If the leaf still tags off NBGV, the wrapper is not actually pinned to upstream.*

### D4 - Release / Publish
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
00945eed67b8dbd4
9c172b84003d3d06
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
- **D3.1 One branch per run.** Input: a publish triggered on `main` or `develop`. Output: the run builds and versions that one branch, and `github.ref` names it, so NBGV classifies it directly (no `IGNORE_GITHUB_REF`). *Prevents: a cross-branch ref mismatch misclassifying the version.*
- **D3.2 Default = public, others = prerelease.** Output: default branch -> `X.Y.Z`, and any other -> `X.Y.Z-g<sha>`. The default-branch literal in the gate, the `prerelease` expression, and `version.json` MUST all name the repo's real default branch.
- **D3.3 Version floor + git height.** Output: `version.json` sets the major.minor floor. NBGV appends the git height as the patch, bumped only for a functional change by the maintainer. NBGV and `version.json` are retained even by a no-compiler repo (they own the tag).
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). The develop `.dev0` build must remain `pip install --pre`-selectable and sort above the default release (NBGV git height in the release segment keeps develop ahead). *Prevents: a non-default leg published as a release, and a renamed/extra branch silently getting a plain version.*
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). *Prevents: a non-default leg published as a release.*
- **D3.5 Wrapper repos may use an external version.** Output: a repo wrapping an upstream release drives its build/image version from a committed `name -> version` state file, while NBGV still tags the release. *Note: the tracker (the writer) ships without consumer wiring, so a wrapper must wire the leaf to read the state file (e.g. `jq` into the image tag) instead of `SemVer2`. If the leaf still tags off NBGV, the wrapper is not actually pinned to upstream.*

### D4 - Release / Publish
Expand Down
2 changes: 0 additions & 2 deletions .github/actions/pypi-build-default/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ runs:

# PEP 440 version: develop gives M.N.P.dev0 as a prerelease, and anything else gives M.N.P.
# M.N.P is SemVer2 with its prerelease and build segments stripped.
# The NBGV git height sits in the release segment, so develop's normally exceeds main's.
# A pip install --pre therefore picks the dev build over the main release.
- name: Compute PyPI version step
id: pypiver
shell: bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
- **D3.1 One branch per run.** Input: a publish triggered on `main` or `develop`. Output: the run builds and versions that one branch, and `github.ref` names it, so NBGV classifies it directly (no `IGNORE_GITHUB_REF`). *Prevents: a cross-branch ref mismatch misclassifying the version.*
- **D3.2 Default = public, others = prerelease.** Output: default branch -> `X.Y.Z`, and any other -> `X.Y.Z-g<sha>`. The default-branch literal in the gate, the `prerelease` expression, and `version.json` MUST all name the repo's real default branch.
- **D3.3 Version floor + git height.** Output: `version.json` sets the major.minor floor. NBGV appends the git height as the patch, bumped only for a functional change by the maintainer. NBGV and `version.json` are retained even by a no-compiler repo (they own the tag).
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). The develop `.dev0` build must remain `pip install --pre`-selectable and sort above the default release (NBGV git height in the release segment keeps develop ahead). *Prevents: a non-default leg published as a release, and a renamed/extra branch silently getting a plain version.*
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). *Prevents: a non-default leg published as a release.*
- **D3.5 Wrapper repos may use an external version.** Output: a repo wrapping an upstream release drives its build/image version from a committed `name -> version` state file, while NBGV still tags the release. *Note: the tracker (the writer) ships without consumer wiring, so a wrapper must wire the leaf to read the state file (e.g. `jq` into the image tag) instead of `SemVer2`. If the leaf still tags off NBGV, the wrapper is not actually pinned to upstream.*

### D4 - Release / Publish
Expand Down
4 changes: 2 additions & 2 deletions WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output
- **D3.1 One branch per run.** Input: a publish triggered on `main` or `develop`. Output: the run builds and versions that one branch, and `github.ref` names it, so NBGV classifies it directly (no `IGNORE_GITHUB_REF`). *Prevents: a cross-branch ref mismatch misclassifying the version.*
- **D3.2 Default = public, others = prerelease.** Output: default branch -> `X.Y.Z`, and any other -> `X.Y.Z-g<sha>`. The default-branch literal in the gate, the `prerelease` expression, and `version.json` MUST all name the repo's real default branch.
- **D3.3 Version floor + git height.** Output: `version.json` sets the major.minor floor. NBGV appends the git height as the patch, bumped only for a functional change by the maintainer. NBGV and `version.json` are retained even by a no-compiler repo (they own the tag).
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). The develop `.dev0` build must remain `pip install --pre`-selectable and sort above the default release (NBGV git height in the release segment keeps develop ahead). *Prevents: a non-default leg published as a release, and a renamed/extra branch silently getting a plain version.*
- **D3.4 Registry versions follow the classification, per registry.** Output: NuGet default = stable, others = prerelease (derived by NuGet.org from the SemVer2 `-g<sha>` suffix on `PackageVersion`, not a flag the workflow sets). PyPI builds from the `X.Y.Z` core of `SemVer2`, any prerelease or build segment dropped, and appends `.dev0` on the `develop` branch only (a two-branch literal, not a generic N-branch rule). *Prevents: a non-default leg published as a release.*
- **D3.5 Wrapper repos may use an external version.** Output: a repo wrapping an upstream release drives its build/image version from a committed `name -> version` state file, while NBGV still tags the release. *Note: the tracker (the writer) ships without consumer wiring, so a wrapper must wire the leaf to read the state file (e.g. `jq` into the image tag) instead of `SemVer2`. If the leaf still tags off NBGV, the wrapper is not actually pinned to upstream.*

### D4 - Release / Publish
Expand Down Expand Up @@ -289,7 +289,7 @@ What each type adds beyond that, and how its leaf behaves, is below.

- **`dotnet-publish`.** The target runs a sequential `dotnet publish` runtime loop inside one composite-action job. Configuration is Release on the default branch and Debug otherwise. A non-smoke run builds the full runtime set, archives the combined output as a `.7z`, and uploads it as `release-asset-<branch>-dotnet-publish`. The archive is named from the project file stem unless `dotnet_publish_asset_name` overrides it. A smoke run builds a **strict, non-empty subset** of that runtime set and skips the archive and upload steps, so it uploads nothing. S1 smoke-builds that subset after a .NET project change. Where the repo has a publisher, S7 attaches the 7z from a non-smoke run. The non-default leg sets `prerelease=true`, and the default leg sets `prerelease=false`. GitHub marks the stable default release "Latest" automatically.
- **`nuget`.** The leaf uploads both `release-asset-<branch>-nuget` and `nuget-build-<branch>` on a non-smoke run and pushes nothing, and a separate `publish-nuget` job in the repo's own publisher consumes the second and runs `dotnet nuget push *.nupkg --skip-duplicate`, then deletes it under the download step's own success (D5.2). Section 3's `Output Seam by Destination` says why the push sits there rather than in the leaf. Configuration is Release on the default branch, Debug otherwise. Where symbols are enabled (`snupkg`), the push auto-carries the paired `.snupkg` to NuGet.org's symbol server and the release-asset `.7z` also contains it, a triple surface. NuGet.org derives `isPrerelease` from the SemVer2 `-g<sha>` suffix (the workflow sets no such flag). Test: S7 non-default leg publishes a prerelease package + asset, default a stable; S9 re-run is a server-side `--skip-duplicate` no-op. 5C: query NuGet.org for both versions and the symbol package.
- **`pypi`.** The leaf builds and uploads `pypi-build-<branch>`. A **separate** `publish-pypi` job (with `environment: pypi`, `id-token: write`, `actions: write`) does the OIDC Trusted-Publishing upload with `skip-existing: true`, then **consume-then-deletes** the build artifact under the download step's own success (D5.2), so on S9 it is deleted even though the `release-asset-*` delete is skipped. The version is the `X.Y.Z` core of `SemVer2` with `.dev0` appended on `develop` only, per D3.4, and must stay `--pre`-selectable and sorted above the default release. PyPI contributes no `release-asset-*`. A PyPI-only repo sets `expect_release_assets: false` at the caller. Test: S7 default leg publishes a release, non-default a `.dev0`; S9 is a `skip-existing` no-op; 5C inspects the `dist/*` filenames and the compute-version log.
- **`pypi`.** The leaf builds and uploads `pypi-build-<branch>`. A **separate** `publish-pypi` job (with `environment: pypi`, `id-token: write`, `actions: write`) does the OIDC Trusted-Publishing upload with `skip-existing: true`, then **consume-then-deletes** the build artifact under the download step's own success (D5.2), so on S9 it is deleted even though the `release-asset-*` delete is skipped. The version is the `X.Y.Z` core of `SemVer2` with `.dev0` appended on `develop` only, per D3.4. PyPI contributes no `release-asset-*`. A PyPI-only repo sets `expect_release_assets: false` at the caller. Test: S7 default leg publishes a release, non-default a `.dev0`; S9 is a `skip-existing` no-op; 5C inspects the `dist/*` filenames and the compute-version log.
- **`docker`.** The leaf pushes the default branch multi-arch (amd64+arm64) and any other branch `amd64`-only, with a per-branch registry buildcache. A single-image repo caches to `<repo>:buildcache-<branch>`, and a multi-image repo varies the cache **repository** rather than the tag, `<image>:buildcache-<branch>` for each image, since the tag alone cannot distinguish two images (`cache-to` writes only the built branch and only on push, `cache-from` reads both branches). It contributes no `release-asset-*`, so a Docker-only repo's caller passes `expect_release_assets: false`. The readme job (`peter-evans/dockerhub-description`, `DOCKER_HUB_ACCESS_TOKEN`) runs **only** when the default branch publishes, whether called directly or reached through the hub-hosted `publish-docker-readme-task.yml`. Where the Docker Hub overview differs from the project README, the repo publishes a `Docker/README.md` through that task, the Hub description being size-limited. The docker-readme task validates its two mutually-exclusive input sources, `repositories` against `manifest`+`manifest-jq`, and defaults to the calling repository where neither is supplied, and a multi-image repo derives its publish matrix from the manifest. Docker **always re-pushes** the image, independently of a skipped release-create (S9). Test: S7 default leg pushes `latest` + the version tag and updates the readme. Non-default pushes the develop tag (amd64 only). S9 still re-pushes. 5C Docker probe needs `DOCKER_HUB_*` secrets and same-repo (not fork) runs.
- **`library` (a worked example, not a fleet type).** No such leaf ships, so this row walks D6.4's add-a-target procedure rather than an existing shape: a single new leaf that validates, zips, and uploads `release-asset-<branch>-library` (`retention-days: 1` per D5.4, upload gated on smoke being false per D1.3). Adding it means a new `enable_library` input, a `build-library` job and its `github-release` and `build-docker` `needs:` entries in the release task, and a `library` paths-filter entry, `changes` output, and `smoke-build` enable-forward in the PR workflow (without that last one, D1.1 never smoke-builds the library). **Only a repo that owns its release task can make the first half of that edit.** The hub-hosted release task declares a closed input set, so a repo calling it can add the paths-filter entry, the output, and the enable-forward in its own PR workflow and nothing else, and adding a target there is a change to the hub task first. Keep `expect_release_assets: true` (it has a file target, unlike Docker). The caller's own validation job is replaced by a type-appropriate validator only where the reusable one cannot express this repo's validation, with the aggregator re-pointed to the replacement (D1.2). `smoke-build` keeps `needs: [changes]`, as D1.2 and the hub's release-with-smoke stub both have it. `version.json` and the NBGV `get-version` **job** are retained (they own the tag). Test: S1 smoke runs validate+zip and uploads nothing; S7 attaches the zip, prerelease on the non-default leg; S9 on a *scheduled* re-run skips release-create and the asset-delete (the existing zip is untouched, no registry push), while a `workflow_dispatch` re-run is D4.4's refresh case rather than S9's, re-uploading then re-deleting the asset.
- **`upstream-wrapper`.** The repo tracks an upstream project's releases rather than versioning its own code. A scheduled resolver writes a `name -> version` state file and opens a per-branch bump pull request, the merge-bot auto-merges it, or leaves it for the maintainer where the tracker sets `auto-merge: false` (D8.3), and the build leaf MUST read that state file for the immutable tag instead of `SemVer2` (the tracker ships without this consumer wiring). Test: S11 traces the bump from resolver to the publish that ships it, which is the `main` pin's next gated publish rather than the merge itself, since D4.1 admits no publish from a human merge. Nothing in this row depends on which build-target types the wrapper also declares.
Expand Down
24 changes: 12 additions & 12 deletions reports/canonical-review.json
Original file line number Diff line number Diff line change
Expand Up @@ -771,11 +771,11 @@
},
{
"unit": ".agents/skills/workflow-ci-contract/references/d-guarantees.md > The Behavioral Contract",
"digest": "sha256:801f9df9c0fc29f8762726898c29076366eb19bdd520fcaea6d92dc1e328c2dd",
"digest": "sha256:f0f7bc15472c9919ed46b8e4883a7a41391651465d2bb424f6c736f3883807ea",
"reviewer": "agent-skill",
"findings": 2,
"hubCommit": "b21059f2deea1f80e8e522d5452de1ec92b33091",
"stamp": "2026-09-11T03:05:58Z"
"findings": 1,
"hubCommit": "a76d389d50bcf4bd17f6bc0ca173c9d0df62d0af",
"stamp": "2026-09-11T13:55:19Z"
},
{
"unit": ".agents/skills/workflow-ci-contract/references/test-methodology.md > (preamble)",
Expand Down Expand Up @@ -1035,11 +1035,11 @@
},
{
"unit": "WORKFLOW.md > 4. Behavioral Contract: Expected Outcomes",
"digest": "sha256:13c460fc28199d34c0b30427e75d361e721204bd7646a33487f1c693b121f828",
"digest": "sha256:8aff932fcb163a3445f3a530832f53f0fc1195dc37d91d173c6f44534038a0bd",
"reviewer": "agent-skill",
"findings": 3,
"hubCommit": "b21059f2deea1f80e8e522d5452de1ec92b33091",
"stamp": "2026-09-11T03:06:11Z"
"findings": 2,
"hubCommit": "a76d389d50bcf4bd17f6bc0ca173c9d0df62d0af",
"stamp": "2026-09-11T13:54:00Z"
},
{
"unit": "WORKFLOW.md > 5. Test Methodology",
Expand All @@ -1051,11 +1051,11 @@
},
{
"unit": "WORKFLOW.md > 6. Per-Project-Type Test Walkthroughs",
"digest": "sha256:7ec9e96919ab17cf5a496a2128b19cb629fd6a241dcc572e909c9b4135b38319",
"digest": "sha256:d9c7fc6f68a3d53922a3467da305755b8f57ce3927afa8b9a0980fc5feee506f",
"reviewer": "agent-skill",
"findings": 7,
"hubCommit": "b21059f2deea1f80e8e522d5452de1ec92b33091",
"stamp": "2026-09-11T03:01:45Z"
"findings": 2,
"hubCommit": "a76d389d50bcf4bd17f6bc0ca173c9d0df62d0af",
"stamp": "2026-09-11T13:38:56Z"
}
]
}