feat(promotion): gate promotion out of an org on a passing check - #66
Open
scott-lowe-vapi wants to merge 1 commit into
Open
scott-lowe-vapi wants to merge 1 commit into
scott-lowe-vapi wants to merge 1 commit into
Conversation
This was referenced Oct 1, 2026
Contributor
Author
This was referenced Oct 1, 2026
scott-lowe-vapi
marked this pull request as ready for review
October 1, 2026 23:52
vtkovapi
approved these changes
Oct 3, 2026
Contributor
Author
scott-lowe-vapi
changed the base branch from
feat/vapi-checks-workflow
to
graphite-base/66
October 3, 2026 06:12
scott-lowe-vapi
force-pushed
the
feat/promotion-check-gate
branch
from
October 3, 2026 06:18
849c59f to
95379be
Compare
scott-lowe-vapi
changed the base branch from
graphite-base/66
to
fix/promotion-commit-applied-transitions
October 3, 2026 06:18
`orgs.<slug>.check: <name>` in promotion.yml names a vapi-checks.yml
check that must pass in that org before any transition promotes out of
it. The gate runs the same inline check as the PR workflow, built from
the source org's files at the promoted commit, using that org's key from
VAPI_PROMOTION_TOKENS.
- Checks are validated before any transition: the named check must exist
and read and run in the gated org.
- Transitions with no changes skip the gate; plan-only runs print
`check would run <name> in <org> (<n> simulations × <t> targets)`
and run nothing.
- On --apply the check runs after bindings refresh and before
promotionPlanApply writes the target. Any non-pass (failed,
incomplete, build error) throws `Promotion out of <org> blocked: check
<name> <outcome> (<run url>)`, so the target is untouched and earlier
transitions are still committed (previous change).
- A pass is reused for later transitions out of the same org in the same
run, and dropped once a transition applies into that org.
- The "Reconcile configured promotions" step gets timeout-minutes: 90 on
the step, not the job, so the always() commit step still runs.
- With no check: configured, promotion is unchanged: a test pins the
plan output to what the pre-gate code prints, and asserts no check runs
and vapi-checks.yml is never read.
- Docs: promotion.example.yml and README ("Check before promoting", and a
pointer from "PR Checks").
Refs TEST-141
Co-Authored-By: Claude Opus 5.5 <[email protected]>
scott-lowe-vapi
force-pushed
the
fix/promotion-commit-applied-transitions
branch
from
October 3, 2026 06:28
ef619e1 to
c07fed1
Compare
scott-lowe-vapi
force-pushed
the
feat/promotion-check-gate
branch
from
October 3, 2026 06:28
95379be to
84daba8
Compare
This was referenced Oct 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Value
V.A.L.U.E. tier: project — PR 10 of 10 for inline simulation PR checks (TEST-141), the "check before deploy" step in promotion.
promotion.yml. Single-org users are unaffected.promotion.ymlacceptsorgs.<slug>.check: <name>(a slug), naming avapi-checks.ymlcheck.src/promotion-gate.ts:organdrunOrgmust be the gated org, otherwise the run errors.vapi-checks.ymlis required once any org is gated.src/promote-cmd.ts: in each transition, after the plan is built:check would run <name> in <org> (<n> simulations × <t> targets);--applyruns the check (after the bindings refresh, beforepromotionPlanApplywrites anything). Any non-pass throwsPromotion out of <org> blocked: check <name> <outcome> (<run url>).promotionCommandRun(args, overrides)now takesPartial<PromotionDeps>(childRun,checkRun)..github/workflows/promotion.yml:timeout-minutes: 90on the "Reconcile configured promotions" step, not the job, so theif: always()commit step (fixed in fix(promotion): commit the files of transitions that applied when a later one fails #65) still runs after a blocked or slow gate.promotion.example.yml(a commentedcheck:), a README "Check before promoting" section, and a pointer from "PR Checks".Evidence of value
The real gate, run live in the owner's test org on the TEST-141 parity squad.
promotion.ymlgatesparityon checkcore, with pipelineparity → parity-prod.promote --pipeline release --from parity --to parity-prod --apply.apply.tswas recorded but not run. No second org was needed or touched.resources/parity-prod/Promotion out of parity blocked: check core failed (https://dashboard.vapi.ai/simulations/run/7ed19587-…)["parity-prod"]The test org's resource counts were identical before and after both gate runs.
Tests:
npm testgoes from 484 (#65) to 492 passing, and #68's golden promotion test passes unchanged.Testing plan
tests/promotion-gate.test.ts(6 tests, real git fixture, injectedchildRun/checkRun):vapi-checks.yml, unknown check, check in another org) stop before anything applies;check:inpromotion.ymland an invalidvapi-checks.ymlpresent, plan and--applyboth succeed,checkRunis never called, and the plan output equals a pinned string. That string is exactly what fix(promotion): commit the files of transitions that applied when a later one fails #65's code (before the gate existed) prints for the same fixture, which I confirmed by running fix(promotion): commit the files of transitions that applied when a later one fails #65'spromote-cmdon it. So the gate is invisible unless someone opts in.tests/promotion.test.ts:orgs.<slug>.checkis parsed, and a non-slug is rejected.Stacked on #65.
Refs TEST-141
🤖 Generated with Claude Code