Skip to content

Commit 866b58e

Browse files
committed
ci: catch governed-manifest drift at merge time and before tagging
The v0.49 publication gates run inside the release workflow's publish job, which is downstream of the full test and build matrices. Any drift in the governed manifests — a merge that touched a governed surface file, a host version bump that moved the prefix capture, a manifest whose source_commit fell behind — therefore surfaced ~20 minutes into a release, after the tag existed, forcing a delete-and-re-cut. Move the same checks to the two places that can act on them: the unit suite (so drift fails on the merge that caused it) and release.sh's pre-release block (so a stale manifest is caught before the tag is created). Both discover their scripts by glob or release line, so a future release line without a gate script skips instead of failing.
1 parent 34c8a3d commit 866b58e

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

.github/workflows/_unit-suite.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,30 @@ jobs:
7777
- name: Bun typecheck
7878
run: bun run typecheck
7979

80+
# Governed publication surfaces (tool schemas, descriptions, workflow
81+
# hints) are byte-pinned in docs/ manifests that live OUTSIDE the normal
82+
# code-change workflow, so a merge touching a governed file leaves them
83+
# stale silently. Unchecked, that drift only surfaces inside the release
84+
# workflow's publish job — after the full test and build matrices have
85+
# run — costing a dead tag and a re-cut. Checking it here fails the merge
86+
# that caused the drift, next to the change that explains it.
87+
#
88+
# Linux-only: the audit reads source bytes and builds tool maps in-process,
89+
# so it is platform-independent and does not need repeating per OS.
90+
- name: Agent surface audit (governed manifest drift)
91+
run: |
92+
set -euo pipefail
93+
shopt -s nullglob
94+
audits=(scripts/audit-v[0-9]*-agent-surface.ts)
95+
if [ ${#audits[@]} -eq 0 ]; then
96+
echo "no versioned agent-surface audit present; skipping"
97+
exit 0
98+
fi
99+
for audit in "${audits[@]}"; do
100+
echo "→ bun $audit"
101+
bun "$audit"
102+
done
103+
80104
- name: Cargo build (debug — needed by plugin e2e tests)
81105
run: cargo build -p agent-file-tools
82106

scripts/release.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,43 @@ bun run typecheck 2>&1 || { echo "Error: Typecheck failed"; exit 1; }
208208
echo " cargo fmt --check..."
209209
cargo fmt --check 2>&1 || { echo "Error: cargo fmt --check failed (run 'cargo fmt')"; exit 1; }
210210

211+
# Publication-surface gates, run LOCALLY before the tag exists.
212+
#
213+
# These same gates run inside the release workflow's publish job, i.e. AFTER
214+
# the full test matrix and the whole build matrix have already burned ~20
215+
# minutes. Any drift in the governed manifests (a merged PR that touched a
216+
# governed surface file, a host version bump that moved the prefix capture, a
217+
# manifest whose source_commit no longer matches) fails there, which means a
218+
# dead tag and a full re-cut. The v0.49.0 release burned eight tag attempts on
219+
# exactly this class before the checks moved here.
220+
#
221+
# Only run when the gate script for the current release line exists: the gates
222+
# are version-scoped artifacts (release-gate-vNNN.mjs), so a later release line
223+
# without one skips this block instead of failing.
224+
release_line="$(printf '%s' "$VERSION" | cut -d. -f1,2)"
225+
line_slug="v${release_line//./}"
226+
gate_script="scripts/release-gate-${line_slug}.mjs"
227+
audit_script="scripts/audit-${line_slug}-agent-surface.ts"
228+
if [[ -f "$gate_script" ]]; then
229+
echo " agent surface audit..."
230+
bun "$audit_script" >/dev/null 2>&1 || {
231+
echo "Error: agent surface audit failed"
232+
echo " ↳ run: bun $audit_script"
233+
echo " ↳ if a governed surface changed on purpose, regenerate with"
234+
echo " --write-allowlist --write-prefix-capture --write-manifest"
235+
exit 1
236+
}
237+
238+
echo " publication gates ($gate_script)..."
239+
node "$gate_script" >/dev/null 2>&1 || {
240+
echo "Error: publication gates failed"
241+
echo " ↳ run: node $gate_script"
242+
echo " ↳ stale manifests regenerate via the audit script; the release"
243+
echo " manifest's source_commit must match the surface manifest's"
244+
exit 1
245+
}
246+
fi
247+
211248
# Slow checks AFTER the fast ones pass.
212249
#
213250
# TRUST_CI=1: skip the local Rust/JS/docker test re-run when CI is already

0 commit comments

Comments
 (0)