Q3b (order 20260912T213049Z-5772222747) corrects two measured defects in
Q3's ea9bf7a: pushWithToken checked-and-consumed per call, so a single
`--create-tag --write --commit --push` run spent the operator's one-shot
token on the tag push and always saw blocked:true on the catalog push
right after (D1). And `git tag -a` ran before any token check at all, so
a blocked run left a local annotated tag behind, breaking the retry with
"tag already exists" (D2).
createPushGate replaces the per-push check-and-consume with a run-scoped
gate: ensure() checks the token once and every later call in the same run
reuses that result, consume() fires once after the run's last successful
push. main() calls ensure() before the tag write (not just before the
push) and consume() once at the end. pushWithToken is now a single-push
convenience wrapper over the same gate — its existing tests stay green
unmodified.
Red-first: `createPushGate` did not exist on ea9bf7a (import error),
proving both new tests were red before the fix. After:
node --test scripts/release-plugin.test.mjs -> 37/37 (35 + 2 new)
node --test scripts/*.test.mjs -> 154/154
node scripts/check-versions.mjs -> 0 ERROR (2 known WARN: claude-design, repo-mailbox)
Live D2 check: `release-plugin.mjs repo-mailbox --create-tag --write`
without a token -> BLOCKED, exit 1, no local tag created.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>