Skip to content
Merged
3 changes: 2 additions & 1 deletion .github/actions/nuget-build-default/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ runs:
path: |
${{ runner.temp }}/publish/*.nupkg
${{ runner.temp }}/publish/*.snupkg
# A project that packs nothing would otherwise only warn, create no artifact at all, cut a release, and fail one job later on a download that cannot say why.
# A project that packs nothing would otherwise only warn and create no artifact at all.
# It would still cut a release, and fail one job later on a download that cannot say why.
if-no-files-found: error
# Consumed within the run by the caller stub's publish-nuget job, so minimize artifact storage.
retention-days: 1
Expand Down
3 changes: 2 additions & 1 deletion .github/actions/pypi-build-default/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ runs:
with:
name: pypi-build-${{ inputs.branch }}
path: ${{ inputs.project-dir }}/dist/*
# A build that emits nothing would otherwise only warn, create no artifact at all, and fail one job later on a download that cannot say why.
# A build that emits nothing would otherwise only warn and create no artifact at all.
# It would fail one job later on a download that cannot say why.
if-no-files-found: error
# Consumed within the run by the caller stub's publish-pypi job, so minimize artifact storage.
retention-days: 1
3 changes: 2 additions & 1 deletion .github/actions/validate-default/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# The hub default for the validate hook, used when a caller carries no .github/actions/validate/action.yml of its own.
# A repo with no domain checks of its own (no ESPHome compile, no Hugo build, no KiCad ERC, no codegen drift, no PowerShell tests) has nothing for this hook to run.
# A repo with no domain checks of its own has nothing for this hook to run.
# These domain checks include an ESPHome compile, a Hugo build, a KiCad ERC, codegen drift, and PowerShell tests.
# The fleet doc-lint block plus the generic unit-test job already cover everything else validate-task.yml checks.
name: Validate repository (default)
description: No-op default for the validate hook.
Expand Down
12 changes: 8 additions & 4 deletions .github/actions/validate/action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# The hub's own validate hook, run by validate-task.yml's validate job for this repo.
# The fleet doc-lint block and the Python leg in validate-task.yml, which runs the unittest suite under tests/, do not cover the rest of the hub's own machinery.
# Carrying this hook makes the hub exercise the override path on every hub pull request, where a repo with no hook of its own exercises the hub default at .github/actions/validate-default.
# The fleet doc-lint block and the Python leg in validate-task.yml do not cover the rest of the hub's own machinery.
# That Python leg runs the unittest suite under tests/.
# Carrying this hook makes the hub exercise the override path on every hub pull request.
# A repo with no hook of its own exercises the hub default at .github/actions/validate-default instead.
name: Validate repository (hub)
description: Registry and spec validation, the audit helper suite and the self-tests outside the unittest suite, the fleet-skills freshness check, and the unclassified-character report.

Expand Down Expand Up @@ -43,9 +45,11 @@ runs:
shell: bash
run: uv run --no-project --script scripts/build_dist.py --check

# The burn-down goes to the run's job summary rather than into the tree, on every event since it describes the commit rather than a comparison against a base.
# The burn-down goes to the run's job summary rather than into the tree.
# It does so on every event, since it describes the commit rather than a comparison against a base.
# GOVERNANCE.md "Verification Discipline".
# The step fails only on the engine's boundary exit, meaning it could not read what it needs rather than having read it and found a unit uncovered, since a boundary is not a verdict about coverage.
# The step fails only on the engine's boundary exit, since a boundary is not a verdict about coverage.
# That exit means the engine could not read what it needs, rather than having read it and found a unit uncovered.
# Read-only, and `!cancelled()` so it renders after an earlier step failed.
- name: Render the canonical review burn-down step
if: ${{ !cancelled() }}
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/build-docker-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ name: Build Docker image task
# The in-job Docker Hub description push that earlier per-repo copies carried is dropped here in favor of publish-docker-readme-task.yml, per docs/reusable-workflows.md "The Docker Family" (stage 5).
# The overview then publishes once per release rather than once per image build.
#
# The image set comes from a docker-prepare hook, the caller's own, or the hub default emitting the single vanilla entry the image input implies, unless the caller passes matrix directly.
# A required docker-build-base hook, with no hub default, runs first when build-base is set, for a multi-image repo whose base layer is built once and shared across every product image.
# Unless the caller passes matrix directly, the image set comes from a docker-prepare hook.
# That is the caller's own, or the hub default emitting the single vanilla entry the image input implies.
# A required docker-build-base hook, with no hub default, runs first when build-base is set.
# It is for a multi-image repo whose base layer is built once and shared across every product image.
# See docs/reusable-workflows.md "The Docker Family" for the design.

on:
Expand Down Expand Up @@ -117,8 +119,8 @@ jobs:
branch: ${{ inputs.branch }}
semver2: ${{ inputs.semver2 }}

# Fails here with a named cause, rather than later inside fromJSON, whichever path produced the matrix
# (the caller's matrix input, a caller's own docker-prepare hook, or the hub default).
# Fails here with a named cause, rather than later inside fromJSON.
# This holds whichever path produced the matrix (the caller's matrix input, a caller's own docker-prepare hook, or the hub default).
- name: Validate Docker build matrix step
env:
MATRIX: ${{ inputs.matrix != '' && inputs.matrix || (steps.hook.outputs.matrix || steps.default.outputs.matrix) }}
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/build-release-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ jobs:
SMOKE: ${{ inputs.smoke }}
run: |
set -Eeuo pipefail
# Refuse a branch git would not accept as a name, whose ':', '[' or control characters could form a workflow command where later steps print it.
# Refuse a branch git would not accept as a name.
# Its ':', '[' or control characters could form a workflow command where later steps print it.
# Both of git's streams are discarded, since it echoes the name it checks.
if ! git check-ref-format --branch "$BRANCH" > /dev/null 2>&1; then
echo "::error::The branch input is not a valid git branch name, so the build is refused."
Expand Down Expand Up @@ -363,10 +364,12 @@ jobs:
if: ${{ inputs.github && !inputs.smoke && !failure() && !cancelled() }}
runs-on: ubuntu-latest
needs: [get-version, validate-release, dotnet-publish, build-nuget, build-pypi, build-release-asset, build-docker]
# The release upload, the artifact-delete cleanup, and the D4.7 dispatch and self-cancel all write with GITHUB_TOKEN, and the caller grants contents: write and actions: write when it sets github: true on a non-smoke run.
# The release upload, the artifact-delete cleanup, and the D4.7 dispatch and self-cancel all write with GITHUB_TOKEN.
# The caller grants contents: write and actions: write when it sets github: true on a non-smoke run.
# A caller publishing only to a registry, or smoke building, leaves this job disabled and grants neither.
# No job-level permissions: block here, because a called job's block is validated against the caller's grant before its if: runs.
# A block naming contents: write would fail every caller that does not grant it at startup, including a smoke build under a read-only pull request token.
# A block naming contents: write would fail every caller that does not grant it at startup.
# That includes a smoke build under a read-only pull request token.

steps:

Expand Down
48 changes: 35 additions & 13 deletions .github/workflows/canonical-review-sweep.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
name: Sweep canonical review coverage action

# The periodic half of GOVERNANCE.md "Verification Discipline"'s carried-content rule.
# This files the work rather than doing it: the read is an adversarial pass by an agent, so a workflow can only name the units it asks for and hand them to a session that can run it.
# This files the work rather than doing it.
# The read is an adversarial pass by an agent.
# A workflow can therefore only name the units it asks for and hand them to a session that can run it.
# Hub-only, since the canonical content is authored here and a carrier has nothing of its own to sweep.
# The issue's label comes from the fleet label set in repo-config/labels.json, so on a repository where repo-config/configure.sh has not applied that set the first run that finds work fails at gh issue create rather than filing an unlabeled issue no later run would find.
# A run that finds no work says nothing about the label either way, since the listing below returns an empty set for a label that does not exist.
# The issue's label comes from the fleet label set in repo-config/labels.json.
# So on a repository where repo-config/configure.sh has not applied that set, the first run that finds work fails at gh issue create.
# It fails rather than filing an unlabeled issue no later run would find.
# A run that finds no work says nothing about the label either way.
# This is because the listing below returns an empty set for a label that does not exist.

on:
schedule:
Expand All @@ -29,14 +34,21 @@ jobs:
steps:

# The ref is named rather than left to the default a scheduled run checks out, which is the default branch.
# The passes this issue asks for are worked on a develop-based branch, per the repo-worktree base rule, so measuring main would name units against text that branch does not hold, refuse each record on a digest the tree has moved past, and re-file the same list every week until a promotion carried the fix.
# The passes this issue asks for are worked on a develop-based branch, per the repo-worktree base rule.
# Measuring main would therefore:
# - name units against text that branch does not hold
# - refuse each record on a digest the tree has moved past
# - re-file the same list every week until a promotion carried the fix
- name: Checkout repository step
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: develop
# Full history, because the backlog slice orders never-read units by their file's last commit and a shallow clone dates every path to the tip commit, which would collapse that order to the unit key and file the alphabet's first units every week.
# Full history, because the backlog slice orders never-read units by their file's last commit and a shallow clone dates every path to the tip commit.
# That would collapse the order to the unit key and file the alphabet's first units every week.
fetch-depth: 0
# The job holds issues: write, and checkout persists that token in .git/config by default, where nothing here needs git authentication: every git call here reads local state, in the step below and inside the engines alike, and every gh call takes GH_TOKEN.
# The job holds issues: write, and checkout persists that token in .git/config by default.
# Nothing here needs git authentication, since every git call here reads local state, in the step below and inside the engines alike.
# Nor does any gh call need it, since every gh call takes GH_TOKEN.
persist-credentials: false

# The engine declares markdown-it-py inline, which uv resolves for it.
Expand All @@ -52,7 +64,10 @@ jobs:
shell: bash
run: |
set -Eeuo pipefail
# The engine's three-valued exit is read rather than folded: 0 is no unit owing a read, 1 is work to file, and 2 is the sweep never having run, which is not a verdict either way.
# The engine's three-valued exit is read rather than folded:
# - 0 is no unit owing a read
# - 1 is work to file
# - 2 is the sweep never having run, which is not a verdict either way
status=0
uv run --no-project --script scripts/canonical_review.py sweep > "$RUNNER_TEMP/sweep.md" || status=$?
if [ "$status" -gt 1 ]; then
Expand All @@ -71,24 +86,30 @@ jobs:
TITLE: Canonical content review sweep
run: |
set -Eeuo pipefail
# The commit this run measured, read from the checkout rather than from GITHUB_SHA, which on a scheduled event names the default branch's head and not the develop tip checked out above.
# The commit this run measured, read from the checkout rather than from GITHUB_SHA.
# On a scheduled event GITHUB_SHA names the default branch's head and not the develop tip checked out above.
head_sha="$(git rev-parse HEAD)"
# A renamed or removed section leaves its pass naming a unit the tree no longer holds.
# The renamed text is read again under its new key, which the work list reaches, where the stranded pass is not.
# Warned rather than filed, since no pass closes an orphan and an issue that cannot be closed is worse than a warning nobody has to act on.
# `.orphanedPasses | length` answers 0 for an absent key rather than failing, so the field is asserted: a renamed one has to stop the step rather than stop the warning.
# Warned rather than filed, since no pass closes an orphan.
# An issue that cannot be closed is worse than a warning nobody has to act on.
# `.orphanedPasses | length` answers 0 for an absent key rather than failing, so the field is asserted.
# A renamed one has to stop the step rather than stop the warning.
orphans="$(uv run --no-project --script scripts/canonical_review.py status | jq 'if has("orphanedPasses") then (.orphanedPasses | length) else error("canonical_review.py status carries no orphanedPasses field") end')"
if [ "$orphans" -gt 0 ]; then
echo "::warning::$orphans recorded pass(es) name a unit this tree no longer holds, so a section was renamed or removed, and a renamed one is read again under its new key in the never-read backlog"
fi
# Every number a write below targets is read here, in this run, rather than constructed.
# Each producer writes to a file rather than into a process substitution, since mapfile reports success whatever the producer did, so a failed listing or filter would read as no open issue and file a second one beside the first.
# Each producer writes to a file rather than into a process substitution, since mapfile reports success whatever the producer did.
# A failed listing or filter would then read as no open issue and file a second one beside the first.
gh issue list --repo "$GITHUB_REPOSITORY" --state open --label "$LABEL" --json number,title > "$RUNNER_TEMP/issues.json"
jq -r '.[].number' "$RUNNER_TEMP/issues.json" > "$RUNNER_TEMP/labeled.txt"
jq -r --arg t "$TITLE" '.[] | select(.title == $t) | .number' "$RUNNER_TEMP/issues.json" > "$RUNNER_TEMP/titled.txt"
mapfile -t labeled < "$RUNNER_TEMP/labeled.txt"
mapfile -t titled < "$RUNNER_TEMP/titled.txt"
# The label carries one open issue per repository, so a second one, or a single one carrying a title this workflow never files under, is a state to report rather than one to pick from.
# The label carries one open issue per repository.
# So a second one is a state to report rather than one to pick from.
# So is a single one carrying a title this workflow never files under.
# Refusing on the second shape is what keeps an issue a human labeled from having its body replaced with the work list.
if [ "${#labeled[@]}" -gt 1 ] || [ "${#labeled[@]}" -ne "${#titled[@]}" ]; then
echo "::error::open $LABEL issue(s) ${labeled[*]} are not the single issue titled '$TITLE' that this workflow files, so which one it owns is undecided and a hand edit is what settles it"
Expand All @@ -103,7 +124,8 @@ jobs:
fi
elif [ -n "$number" ]; then
# Closed rather than left open, so the issue's own state answers whether any unit is owed a read.
# A unit that owes a read again is filed as a new issue rather than reopening this one, since the listing above reads open issues only.
# A unit that owes a read again is filed as a new issue rather than reopening this one.
# This is because the listing above reads open issues only.
gh issue comment "$number" --repo "$GITHUB_REPOSITORY" --body "Every carried canonical unit has been read at its current text, measured on develop at $head_sha. A unit that moves past its pass, and one newly authored or newly carried, is filed as a new sweep issue."
gh issue close "$number" --repo "$GITHUB_REPOSITORY"
else
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/check-upstream-version-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ name: Check upstream version task

# Upstream-version tracker for a wrapper repo, hosted here once.
# A required resolve-upstream hook sets a versions step output, a JSON object of name -> version, one key or several for a multi-component pin.
# The task writes that object to a committed state file beside version.json, diffs it against the prior state, and opens a rolling App-signed bump PR per branch that the merge-bot auto-merges.
# The task:
# - writes that object to a committed state file beside version.json
# - diffs it against the prior state
# - opens a rolling App-signed bump PR per branch that the merge-bot auto-merges
# Call it from a scheduled entry-point workflow, and matrix only the branches that ship the version, since a CI-only version uses ["develop"].
# A tracker whose bump waits for a human sets auto-merge false.
# The head then carries a marker no merge-bot rule matches, whatever bump-branch-prefix names, so the pull request needs a maintainer decision instead of merging on its own.
# The head then carries a marker no merge-bot rule matches, whatever bump-branch-prefix names.
# The pull request therefore needs a maintainer decision instead of merging on its own.
# With auto-merge true the default bump-branch-prefix, upstream-version, matches the merge-bot task's built-in upstream-version-main and upstream-version-develop pairs.

on:
Expand Down Expand Up @@ -43,7 +47,9 @@ on:

jobs:

# No job-level permissions declared: every write here (the checkout, the pull request) uses the App token, so GITHUB_TOKEN needs no scope and the caller can grant permissions: {}.
# No job-level permissions declared.
# This is because every write here (the checkout, the pull request) uses the App token.
# So GITHUB_TOKEN needs no scope and the caller can grant permissions: {}.
check-upstream-version:
name: Check upstream version job
runs-on: ubuntu-latest
Expand Down
Loading
Loading