From 361786621274929c294c0e0d481ea0b4dd31edff Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 5 May 2026 08:04:46 +0200 Subject: [PATCH 1/2] fix(checkpoints): standardize on target/pattern schema, broaden cosign formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runner expects field names target: and pattern: (with command: as alias for type=command). github-release was using path:/content:/command:, so the runner skipped every check with 'Target file not found:'. Convert all 13 checkpoints to the standard schema and they actually run again. Side fixes: - GR-1..GR-13 now run against repos that delegate to the netresearch skill-repo-skill release workflow, because the local release.yml grants id-token + attestations + tag triggers (which is exactly what the reusable workflow needs). Inline patterns that match the local file prove the local file is correctly wired into the reusable workflow. - GR-5 demoted to info: the upstream skill-repo-skill release workflow is intentionally tag-push-only because workflow_dispatch + auto-bump produced unsigned tags. Inline workflows that opt in are fine, but it's not required. - GR-6 reformulated without $() / ; / && (forbidden by the runner's command whitelist). Still asserts no lightweight tags. - GR-7 / GR-12 / GR-13 reference vendor/bin/ paths (the runner only whitelists vendor/bin/* — $CLAUDE_PLUGIN_ROOT/scripts is rejected). These checkpoints work when the validator scripts are installed there. - GR-10 (cosign signature) now accepts .bundle (modern Sigstore) OR .sig + .pem (the detached format actually produced by the netresearch skill-repo-skill release workflow). Signed-off-by: Sebastian Mendel --- skills/github-release/checkpoints.yaml | 80 ++++++++++++++++++-------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/skills/github-release/checkpoints.yaml b/skills/github-release/checkpoints.yaml index 9c913ab..9a7fd0d 100644 --- a/skills/github-release/checkpoints.yaml +++ b/skills/github-release/checkpoints.yaml @@ -3,85 +3,117 @@ skill_id: github-release preconditions: - type: file_exists - path: ".git" - message: "Must be inside a git repository" + target: ".git" mechanical: # Release workflow checks - id: GR-1 type: file_exists + target: ".github/workflows/release.yml" severity: error desc: "Release workflow must exist" - path: ".github/workflows/release.yml" - id: GR-2 type: contains + target: ".github/workflows/release.yml" + pattern: "tags:" severity: error desc: "Release workflow must trigger on version tags" - path: ".github/workflows/release.yml" - content: "tags:" - id: GR-3 type: contains + target: ".github/workflows/release.yml" + pattern: "id-token: write" severity: error desc: "Release workflow must have id-token write permission for signing" - path: ".github/workflows/release.yml" - content: "id-token: write" - id: GR-4 type: contains + target: ".github/workflows/release.yml" + pattern: "attestations: write" severity: error desc: "Release workflow must have attestations write permission" - path: ".github/workflows/release.yml" - content: "attestations: write" - id: GR-5 type: contains - severity: warning - desc: "Release workflow should have workflow_dispatch for manual triggers" - path: ".github/workflows/release.yml" - content: "workflow_dispatch" + target: ".github/workflows/release.yml" + pattern: "workflow_dispatch" + severity: info + desc: >- + Release workflow may have workflow_dispatch for manual triggers (info + only — netresearch skill-repo release workflow is intentionally + tag-push-only because workflow_dispatch + auto-bump produced unsigned + tags. Inline workflows that build on it can opt-in safely). # Tag integrity checks - id: GR-6 type: command + pattern: "git for-each-ref refs/tags/v* --format='%(objecttype) %(refname:short)' | grep -v '^tag '" severity: error - desc: "All version tags must be annotated (not lightweight)" - command: "test -z \"$(git for-each-ref refs/tags/v* --format='%(objecttype) %(refname:short)' | grep '^commit ')\"" + desc: >- + All version tags must be annotated (not lightweight). Pipeline emits + anything that is NOT an annotated tag — `!` (PIPELINE) inverts so the + check passes only when no offenders are found. # Version sync checks - id: GR-7 type: command + pattern: "vendor/bin/validate-pre-release.sh --version-sync-only 2>/dev/null" severity: warning - desc: "Version files must be in sync" - command: "${CLAUDE_PLUGIN_ROOT}/skills/github-release/scripts/validate-pre-release.sh --version-sync-only 2>/dev/null" + desc: >- + Version files must be in sync. Requires the github-release skill's + validate-pre-release.sh to be installed at vendor/bin/ (the runner's + command whitelist disallows ${CLAUDE_PLUGIN_ROOT} paths). # CHANGELOG checks - id: GR-8 type: contains + target: "CHANGELOG.md" + pattern: "[Unreleased]" severity: warning desc: "CHANGELOG.md must have an Unreleased section" - path: "CHANGELOG.md" - content: "[Unreleased]" # Supply chain security checks - id: GR-9 type: command + pattern: "gh release view --json assets --jq '[.assets[].name | select(test(\"sbom\"))] | length > 0' 2>/dev/null" severity: warning desc: "Latest published release should have SBOM assets" - command: "gh release view --json assets --jq '[.assets[].name | select(test(\"sbom\"))] | length > 0' 2>/dev/null || echo true" - id: GR-10 type: command + pattern: "gh release view --json assets --jq '[.assets[].name | select(test(\"\\\\.(bundle|sig|pem)$\"))] | length > 0' 2>/dev/null" severity: warning - desc: "Latest published release should have cosign signature bundles" - command: "gh release view --json assets --jq '[.assets[].name | select(test(\"\\.bundle$\"))] | length > 0' 2>/dev/null || echo true" + desc: >- + Latest published release should have cosign signature artefacts: + modern `.bundle` (Sigstore bundle) OR detached `.sig` + `.pem` + (used by netresearch/skill-repo-skill release workflow). - id: GR-11 type: command + pattern: "git config user.signingkey >/dev/null 2>&1" severity: info - desc: "Git signing is configured" - command: "git config user.signingkey >/dev/null 2>&1 || git config gpg.format >/dev/null 2>&1" + desc: "Git signing is configured (signing key set)" + + # Release pipeline integrity checks + - id: GR-12 + type: command + pattern: "vendor/bin/validate-reusable-workflows.sh" + severity: error + desc: >- + Reusable-workflow reference in `.github/workflows/release*.yml` files + points to a path that no longer exists at the pinned SHA. Release + workflow will fail on tag push. Update the ref or remove the job. + Requires the validator at vendor/bin/. + + - id: GR-13 + type: command + pattern: "vendor/bin/check-changelog-links.py" + severity: warning + desc: >- + CHANGELOG.md reference-style header [X.Y.Z] missing matching + [X.Y.Z]: footer link. Add the link entry and update the + [Unreleased]: compare/vX.Y.Z...HEAD range to the new version. # Release pipeline integrity checks - id: GR-12 From 88972c18a715a94e7750929247623f4ade16de3d Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 5 May 2026 12:02:40 +0200 Subject: [PATCH 2/2] fix(checkpoints): correct GR-6/9/10 exit-code semantics, drop legacy duplicates Address reviewer findings on the checkpoint schema standardization: - GR-6: wrap the lightweight-tag detector in `test -z "$(...)"` so the command exits 0 only when no offenders are found. The bare `grep -v` pattern inverted the meaning (exit 0 when offenders exist) and exited 1 on empty output (no tags at all). Now: annotated-only -> 0, any lightweight -> 1, empty repo -> 0. - GR-9: add `jq -e` so a missing SBOM asset produces a non-zero exit instead of printing the literal string "false" with exit 0, which the runner would treat as success. - GR-10: same `jq -e` fix, plus tighten the predicate to require either a `.bundle` OR the pair (`.sig` AND `.pem`). The previous regex `\\.(bundle|sig|pem)$` accepted a lone `.sig` or lone `.pem`, which is not a valid cosign signature set. - Drop the legacy old-schema GR-12/GR-13 entries that re-appeared after rebasing onto main (main introduced them with path:/command:; this branch already carries the target:/pattern: replacements). Removing the duplicates resolves the validator's "duplicate checkpoint IDs" failure. Verified locally: - `python3 -c "yaml.safe_load(...)"` + dup-id check from netresearch/skill-repo-skill validate.yml: 13 mechanical + 3 llm checkpoints, no duplicates. - `yamllint` (with the validator's inline config) clean. - Manual jq/test runs cover bundle-only, sig+pem, lone-sig, lone-pem, none, annotated-only tags, lightweight tags, and empty repo. Signed-off-by: Sebastian Mendel --- skills/github-release/checkpoints.yaml | 43 ++++++++++---------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/skills/github-release/checkpoints.yaml b/skills/github-release/checkpoints.yaml index 9a7fd0d..2aa6d93 100644 --- a/skills/github-release/checkpoints.yaml +++ b/skills/github-release/checkpoints.yaml @@ -48,12 +48,14 @@ mechanical: # Tag integrity checks - id: GR-6 type: command - pattern: "git for-each-ref refs/tags/v* --format='%(objecttype) %(refname:short)' | grep -v '^tag '" + pattern: "test -z \"$(git for-each-ref refs/tags/v* --format='%(objecttype) %(refname:short)' | grep -v '^tag ')\"" severity: error desc: >- - All version tags must be annotated (not lightweight). Pipeline emits - anything that is NOT an annotated tag — `!` (PIPELINE) inverts so the - check passes only when no offenders are found. + All version tags must be annotated (not lightweight). The `grep -v` + pipeline emits any tag whose objecttype is NOT `tag` (i.e. lightweight + commit-tags). Wrapping in `test -z "$(...)"` makes the command exit 0 + only when the pipeline is empty — i.e. all tags are annotated, or no + tags exist at all. # Version sync checks - id: GR-7 @@ -76,18 +78,24 @@ mechanical: # Supply chain security checks - id: GR-9 type: command - pattern: "gh release view --json assets --jq '[.assets[].name | select(test(\"sbom\"))] | length > 0' 2>/dev/null" + pattern: "gh release view --json assets --jq -e 'any(.assets[].name; test(\"sbom\"))' >/dev/null 2>&1" severity: warning - desc: "Latest published release should have SBOM assets" + desc: >- + Latest published release should have SBOM assets. `jq -e` exits with + code 1 when no asset name matches, so the runner sees a real failure + instead of a literal "false" stdout that would otherwise count as a + successful zero-exit. - id: GR-10 type: command - pattern: "gh release view --json assets --jq '[.assets[].name | select(test(\"\\\\.(bundle|sig|pem)$\"))] | length > 0' 2>/dev/null" + pattern: "gh release view --json assets --jq -e '[.assets[].name] as $n | ($n|any(test(\"\\\\.bundle$\"))) or ($n|any(test(\"\\\\.sig$\")) and ($n|any(test(\"\\\\.pem$\"))))' >/dev/null 2>&1" severity: warning desc: >- Latest published release should have cosign signature artefacts: - modern `.bundle` (Sigstore bundle) OR detached `.sig` + `.pem` - (used by netresearch/skill-repo-skill release workflow). + either a modern `.bundle` (Sigstore bundle), or BOTH a detached `.sig` + and matching `.pem` certificate (used by netresearch/skill-repo-skill + release workflow). A lone `.sig` or lone `.pem` is incomplete and + fails. `jq -e` ensures the boolean is reflected in the exit code. - id: GR-11 type: command @@ -115,23 +123,6 @@ mechanical: [X.Y.Z]: footer link. Add the link entry and update the [Unreleased]: compare/vX.Y.Z...HEAD range to the new version. - # Release pipeline integrity checks - - id: GR-12 - type: command - severity: error - desc: >- - Reusable-workflow reference in `.github/workflows/release*.yml` files - (override the default glob via the WORKFLOW_GLOB env var) points to a - path that no longer exists at the pinned SHA. Release workflow will - fail on tag push. Update the ref or remove the job. - command: "${CLAUDE_PLUGIN_ROOT}/skills/github-release/scripts/validate-reusable-workflows.sh" - - - id: GR-13 - type: command - severity: warning - desc: "CHANGELOG.md reference-style header [X.Y.Z] missing matching [X.Y.Z]: footer link. Add the link entry and update the [Unreleased]: compare/vX.Y.Z...HEAD range to the new version." - command: "python3 ${CLAUDE_PLUGIN_ROOT}/skills/github-release/scripts/check-changelog-links.py" - llm_reviews: - id: GR-R1 domain: release-safety