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 @@ -9,7 +9,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D1.3** Smoke never publishes and never uploads: full compile/lint/test, no pushes, every `upload-artifact` gated on smoke being false, `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings. Prevents a PR publishing and orphaned artifacts.
- **D1.4** A PR changing only `.github/workflows/**` is not smoke-built, since an inclusion list satisfying D1.1 matches no workflow path, and actionlint still validates them.
- **D1.5** One required aggregator gates merge: `if: always()`, `needs:` the validation job plus the `changes` and `smoke-build` jobs wherever the repo has a smoke build, passes on skipped smoke, blocks on failure or cancelled, and its name is ruleset-bound (job `name:` equals ruleset `context:`, renamed together).
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output.
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, the upload best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output. The Python invocation, `pytest --cov-report=xml`, names a report format and selects nothing to measure, so a Python repo with tests, lint-only excepted, carries `pytest-cov` in a dev group, selects the coverage source in its own `pyproject.toml`, an `addopts` entry of `--cov=<package>` in practice, and leaves the report at the repository root as `coverage.xml`. The hub validator's Python leg, which runs where that root carries `pyproject.toml`, `tests/`, and `uv.lock`, reds its test step when no such report was written.

## D2: Validation at Entry

Expand All @@ -32,7 +32,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D4.2** `target_commitish` is the built commit's SHA (NBGV `GitCommitId`), never a branch name and never a separately re-resolved ref.
- **D4.3** Every release is a tag plus source zip, README, and LICENSE, `prerelease` equals `branch != default`, file targets attach `release-asset-*`, and a no-file-target caller (Docker-only, PyPI-only, source-only) passes `expect_release_assets: false` or the release-create step fails on unmatched files, a source-only one setting every `enable_*` input false with it. A NuGet caller is not one of those, since its leaf uploads a `release-asset-*` carrying the package.
- **D4.4** No-op republish on a schedule or push trigger: an unchanged version re-pushes nothing and the release-create skips when the tag exists, while a dispatch re-run refreshes it and runs the paired asset delete with it, registries dedupe server-side under `dotnet nuget push --skip-duplicate` and PyPI's `skip-existing: true`, and Docker always re-pushes by design.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch rather than a cleanup, and it repairs that version only while the branch tip has not moved, since NBGV derives the version from git height, so a re-dispatch after the tip has moved publishes a further version and leaves the first release without its package.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch while the tip has not moved, since a dispatch names a branch rather than a commit and so builds that branch's tip at dispatch time. Once the tip has moved a re-dispatch builds the new tip instead, and NBGV deriving the version from git height makes that a further version, so the version whose push failed never reaches the registry. **Re-run all jobs** is the recovery there: GitHub replays under the original event's `GITHUB_SHA` and re-executes every job, and the publisher pins the build to that commit, so the same version is rebuilt, its package artifact rebuilt and re-uploaded rather than left missing by D5.2's delete, and its push retried, the release itself needing nothing from the re-run. Three bounds. D4.4's no-op re-run assumes the earlier push succeeded, so it does not describe this one. GitHub offers a re-run only within 30 days of the initial run. And **Re-run failed jobs** is unreliable rather than unavailable, D5.2's delete usually having taken the artifact its download needs while D5.3 leaves that delete best-effort.
Comment thread
ptr727 marked this conversation as resolved.
- **D4.6** A deploy check asserts which release and which environment answer, waiting for convergence to a bounded timeout, with an unreachable host reported distinctly from an HTTP status.

## D5: Resource Cleanup
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9cf4ad34f6781963
48c6cd114e4cc195
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D1.3** Smoke never publishes and never uploads: full compile/lint/test, no pushes, every `upload-artifact` gated on smoke being false, `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings. Prevents a PR publishing and orphaned artifacts.
- **D1.4** A PR changing only `.github/workflows/**` is not smoke-built, since an inclusion list satisfying D1.1 matches no workflow path, and actionlint still validates them.
- **D1.5** One required aggregator gates merge: `if: always()`, `needs:` the validation job plus the `changes` and `smoke-build` jobs wherever the repo has a smoke build, passes on skipped smoke, blocks on failure or cancelled, and its name is ruleset-bound (job `name:` equals ruleset `context:`, renamed together).
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output.
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, the upload best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output. The Python invocation, `pytest --cov-report=xml`, names a report format and selects nothing to measure, so a Python repo with tests, lint-only excepted, carries `pytest-cov` in a dev group, selects the coverage source in its own `pyproject.toml`, an `addopts` entry of `--cov=<package>` in practice, and leaves the report at the repository root as `coverage.xml`. The hub validator's Python leg, which runs where that root carries `pyproject.toml`, `tests/`, and `uv.lock`, reds its test step when no such report was written.

## D2: Validation at Entry

Expand All @@ -32,7 +32,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D4.2** `target_commitish` is the built commit's SHA (NBGV `GitCommitId`), never a branch name and never a separately re-resolved ref.
- **D4.3** Every release is a tag plus source zip, README, and LICENSE, `prerelease` equals `branch != default`, file targets attach `release-asset-*`, and a no-file-target caller (Docker-only, PyPI-only, source-only) passes `expect_release_assets: false` or the release-create step fails on unmatched files, a source-only one setting every `enable_*` input false with it. A NuGet caller is not one of those, since its leaf uploads a `release-asset-*` carrying the package.
- **D4.4** No-op republish on a schedule or push trigger: an unchanged version re-pushes nothing and the release-create skips when the tag exists, while a dispatch re-run refreshes it and runs the paired asset delete with it, registries dedupe server-side under `dotnet nuget push --skip-duplicate` and PyPI's `skip-existing: true`, and Docker always re-pushes by design.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch rather than a cleanup, and it repairs that version only while the branch tip has not moved, since NBGV derives the version from git height, so a re-dispatch after the tip has moved publishes a further version and leaves the first release without its package.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch while the tip has not moved, since a dispatch names a branch rather than a commit and so builds that branch's tip at dispatch time. Once the tip has moved a re-dispatch builds the new tip instead, and NBGV deriving the version from git height makes that a further version, so the version whose push failed never reaches the registry. **Re-run all jobs** is the recovery there: GitHub replays under the original event's `GITHUB_SHA` and re-executes every job, and the publisher pins the build to that commit, so the same version is rebuilt, its package artifact rebuilt and re-uploaded rather than left missing by D5.2's delete, and its push retried, the release itself needing nothing from the re-run. Three bounds. D4.4's no-op re-run assumes the earlier push succeeded, so it does not describe this one. GitHub offers a re-run only within 30 days of the initial run. And **Re-run failed jobs** is unreliable rather than unavailable, D5.2's delete usually having taken the artifact its download needs while D5.3 leaves that delete best-effort.
- **D4.6** A deploy check asserts which release and which environment answer, waiting for convergence to a bounded timeout, with an unreachable host reported distinctly from an HTTP status.

## D5: Resource Cleanup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D1.3** Smoke never publishes and never uploads: full compile/lint/test, no pushes, every `upload-artifact` gated on smoke being false, `!inputs.smoke` at the workflow layer and `inputs.smoke != 'true'` in a composite action, whose inputs are strings. Prevents a PR publishing and orphaned artifacts.
- **D1.4** A PR changing only `.github/workflows/**` is not smoke-built, since an inclusion list satisfying D1.1 matches no workflow path, and actionlint still validates them.
- **D1.5** One required aggregator gates merge: `if: always()`, `needs:` the validation job plus the `changes` and `smoke-build` jobs wherever the repo has a smoke build, passes on skipped smoke, blocks on failure or cancelled, and its name is ruleset-bound (job `name:` equals ruleset `context:`, renamed together).
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output.
- **D1.6** Coverage reports to Codecov for C# and Python repos with tests, a lint-only profile for that type excepted, the upload best-effort so an outage never reds the gate, with a `codecov.yml` setting statuses informational and `.gitignore` excluding coverage output. The Python invocation, `pytest --cov-report=xml`, names a report format and selects nothing to measure, so a Python repo with tests, lint-only excepted, carries `pytest-cov` in a dev group, selects the coverage source in its own `pyproject.toml`, an `addopts` entry of `--cov=<package>` in practice, and leaves the report at the repository root as `coverage.xml`. The hub validator's Python leg, which runs where that root carries `pyproject.toml`, `tests/`, and `uv.lock`, reds its test step when no such report was written.

## D2: Validation at Entry

Expand All @@ -32,7 +32,7 @@ Each guarantee is a MUST from `WORKFLOW.md` section 4, stated as the output a co
- **D4.2** `target_commitish` is the built commit's SHA (NBGV `GitCommitId`), never a branch name and never a separately re-resolved ref.
- **D4.3** Every release is a tag plus source zip, README, and LICENSE, `prerelease` equals `branch != default`, file targets attach `release-asset-*`, and a no-file-target caller (Docker-only, PyPI-only, source-only) passes `expect_release_assets: false` or the release-create step fails on unmatched files, a source-only one setting every `enable_*` input false with it. A NuGet caller is not one of those, since its leaf uploads a `release-asset-*` carrying the package.
- **D4.4** No-op republish on a schedule or push trigger: an unchanged version re-pushes nothing and the release-create skips when the tag exists, while a dispatch re-run refreshes it and runs the paired asset delete with it, registries dedupe server-side under `dotnet nuget push --skip-duplicate` and PyPI's `skip-existing: true`, and Docker always re-pushes by design.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch rather than a cleanup, and it repairs that version only while the branch tip has not moved, since NBGV derives the version from git height, so a re-dispatch after the tip has moved publishes a further version and leaves the first release without its package.
- **D4.5** A failed build blocks every publish target: `github-release` needs every build and the terminal registry pusher (Docker) needs every other build, both guarding `!failure() && !cancelled()` so a disabled or unchanged target, skipped rather than failed, still lets the release be cut and the image pushed, and a package target's separate `publish-<target>` job `needs:` the release-task call, so no build failure ships anything partial. A failed **package** push is outside that: the `publish-<target>` job runs after the whole release task and so after `github-release`, and can leave a release and tag for a version the registry never received. The recovery is a re-dispatch while the tip has not moved, since a dispatch names a branch rather than a commit and so builds that branch's tip at dispatch time. Once the tip has moved a re-dispatch builds the new tip instead, and NBGV deriving the version from git height makes that a further version, so the version whose push failed never reaches the registry. **Re-run all jobs** is the recovery there: GitHub replays under the original event's `GITHUB_SHA` and re-executes every job, and the publisher pins the build to that commit, so the same version is rebuilt, its package artifact rebuilt and re-uploaded rather than left missing by D5.2's delete, and its push retried, the release itself needing nothing from the re-run. Three bounds. D4.4's no-op re-run assumes the earlier push succeeded, so it does not describe this one. GitHub offers a re-run only within 30 days of the initial run. And **Re-run failed jobs** is unreliable rather than unavailable, D5.2's delete usually having taken the artifact its download needs while D5.3 leaves that delete best-effort.
- **D4.6** A deploy check asserts which release and which environment answer, waiting for convergence to a bounded timeout, with an unreachable host reported distinctly from an HTTP status.

## D5: Resource Cleanup
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/validate-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,19 @@ jobs:
if: hashFiles('pyproject.toml') != '' && hashFiles('tests/**') != '' && hashFiles('uv.lock') != ''
run: uv sync --all-groups --frozen

# --cov-report=xml names the report format and selects nothing to measure, so the repository's own pyproject.toml supplies the --cov selector, per D1.6.
# The report is checked rather than assumed, because the best-effort upload below reads a missing file exactly as it reads a healthy run.
# The pre-run delete makes that a check on what this run wrote, since a committed coverage.xml would otherwise satisfy it without any measurement.
- name: Run pytest step
if: hashFiles('pyproject.toml') != '' && hashFiles('tests/**') != '' && hashFiles('uv.lock') != ''
run: uv run pytest --cov-report=xml
run: |
set -Eeuo pipefail
rm -f coverage.xml
uv run pytest --cov-report=xml
if [[ ! -s coverage.xml ]]; then
echo "::error::This run wrote no coverage.xml at the repository root. Select a coverage source in this repository's pyproject.toml, an addopts entry of --cov=<package> in practice, since --cov-report=xml alone measures nothing, and leave the report at the root path the upload step below reads."
exit 1
fi

# Best-effort: continue-on-error plus fail_ci_if_error false, so a missing token never reds the gate.
- name: Upload coverage to Codecov step (Python)
Expand Down
Loading