From 66096cdcab3b3616aa2b0949aecd2fccea3fb62b Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Mon, 27 Jul 2026 08:51:09 +1000 Subject: [PATCH] ci: cover models.json in the alignment guardrail; fix release.yml's no-CHANGELOG path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CI-layer fixes found during the v3.25.0 review. Same layer, both small, so they land together (Iron Rule 11). #198 — alignment.yml did not run on a models.json-only PR. The `paths:` filter listed server.mjs / setup.mjs / scripts / lib / ocp / ocp-connect, but not models.json. That made CLAUDE.md hard-requirement #2 ("CI blacklist pass") vacuous for exactly the PRs that change model routing: confirmed on #192, where only gitleaks and test-features ran. models.json is not inert data. It drives MODEL_MAP and VALID_MODELS, the default request model, and — since ADR 0009 — the global MAX_PROMPT_CHARS truncation budget. A models.json-only PR can therefore change server.mjs's runtime behavior with the guardrail never running. models.schema.json is included for the same reason one level up: loosening the schema is what would let a malformed models.json through. Adding paths only widens coverage; the blacklist grep still scans server.mjs, so this costs nothing and closes the gap. Per CLAUDE.md this is a PR amendment to alignment.yml, which is the sanctioned way to change it (removing entries would need an ALIGNMENT.md amendment; nothing is removed here). #202 — release.yml's no-CHANGELOG fallback would have failed the release job. The branch wrote an output named `notes` and exited WITHOUT creating /tmp/release-notes.md, while the create step hard-codes `--notes-file /tmp/release-notes.md`. So the one path that exists to "degrade to minimal notes" instead produced a failed release. Verified both directions by extracting the step's actual script from the YAML and running it, rather than reading it: PRE-FIX, no CHANGELOG -> exit 0, GITHUB_OUTPUT="notes=Release v3.25.0", /tmp/release-notes.md MISSING -> gh release create --notes-file WOULD FAIL POST-FIX, no CHANGELOG -> exit 0, notes_file set, file present ("Release v3.25.0") POST-FIX, with CHANGELOG -> file present, first line "## v3.25.0 — 2026-07-27" Fixed by writing the file in that branch, and by removing the hard-coded-path coupling entirely: both branches now set `notes_file` and the create step consumes `steps.notes.outputs.notes_file`. That output existed already and was never read — the two only agreed by accident. Also added `set -euo pipefail` (the step previously ran unset-tolerant) and an echo of the resolved notes before creating the release, so a wrong-looking body is visible in the job log instead of only on the published release. NOT changed: the empty-extraction guard. My issue text suggested adding one, but `if [ ! -s "$NOTES" ]` was already there and already handles a heading mismatch. Correcting that claim on #202 rather than taking credit for it. Both workflows re-validated as YAML. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017gbqUZ8HfBZpjjbzQ85oH8 --- .github/workflows/alignment.yml | 8 ++++++++ .github/workflows/release.yml | 19 +++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/alignment.yml b/.github/workflows/alignment.yml index ca65d1d..5764bf7 100644 --- a/.github/workflows/alignment.yml +++ b/.github/workflows/alignment.yml @@ -9,6 +9,14 @@ on: - 'lib/**' - 'ocp' - 'ocp-connect' + # models.json is NOT inert data: it drives MODEL_MAP and VALID_MODELS, the default + # request model, and — since ADR 0009 — the global MAX_PROMPT_CHARS truncation budget. + # A models.json-only PR can therefore change server.mjs's runtime behavior, and before + # this the guardrail simply did not run on one (#198). models.schema.json is included + # for the same reason one level up: loosening the schema is what would let a bad + # models.json through. + - 'models.json' + - 'models.schema.json' - '.github/workflows/alignment.yml' jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90c1e1c..e5cc4bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,24 +21,31 @@ jobs: - name: Extract CHANGELOG section id: notes run: | + set -euo pipefail VERSION="${{ steps.ver.outputs.version }}" + NOTES=/tmp/release-notes.md # Extract section for this version from CHANGELOG.md # Pattern: "## v${VERSION}" through the next "## " or EOF if [ ! -f CHANGELOG.md ]; then echo "No CHANGELOG.md found; using minimal release notes" - echo "notes=Release v${VERSION}" >> $GITHUB_OUTPUT + # MUST write the file, not just an output: the create step consumes a FILE, so an + # early exit here used to leave --notes-file pointing at a path that never existed, + # turning "degrade to minimal notes" into a failed release job (#202). + echo "Release v${VERSION}" > "$NOTES" + echo "notes_file=$NOTES" >> $GITHUB_OUTPUT exit 0 fi awk -v ver="v${VERSION}" ' $0 ~ "^## " ver { found=1; print; next } found && /^## v/ { exit } found { print } - ' CHANGELOG.md > /tmp/release-notes.md - if [ ! -s /tmp/release-notes.md ]; then + ' CHANGELOG.md > "$NOTES" + if [ ! -s "$NOTES" ]; then echo "No matching section in CHANGELOG for v${VERSION}; using minimal notes" - echo "Release v${VERSION}" > /tmp/release-notes.md + echo "Release v${VERSION}" > "$NOTES" fi - echo "notes_file=/tmp/release-notes.md" >> $GITHUB_OUTPUT + echo "--- release notes (${VERSION}) ---"; cat "$NOTES" + echo "notes_file=$NOTES" >> $GITHUB_OUTPUT - name: Create GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -49,5 +56,5 @@ jobs: fi gh release create "v${{ steps.ver.outputs.version }}" \ --title "v${{ steps.ver.outputs.version }}" \ - --notes-file /tmp/release-notes.md \ + --notes-file "${{ steps.notes.outputs.notes_file }}" \ --latest