fix(promotion): commit the files of transitions that applied when a later one fails - #65
Conversation
308b8d5 to
ef619e1
Compare
…ater one fails When a promotion failed partway, the workflow's commit step staged only the UUID state, but the failing transition had already rewritten tracked files in its target org, so `git pull --rebase` refused and nothing was pushed — not the state, and not the files of transitions that had already reached the platform. - promote-cmd truncates tmp/promotion-applied.txt at the start of each --apply run and, after each successful apply.ts, records every path git reports changed under resources/<target>/ (from git, not the plan: apply's own pull and push can rewrite other files) with its content at that moment, written to git's object store (`-` for a deletion). - On a non-success outcome, the commit step stages the state files plus exactly those recorded blobs, commits, then resets and cleans resources/ so the failed transition's rewrites can't block the rebase. Staging the recorded content rather than the working tree means a later failed transition that rewrote the same file (two pipelines promoting into one org in one --all run) can't replace what was applied. - promotionCommandRun(args, deps) takes an injectable child runner. - tests/promotion-workflow-commit.test.ts runs the workflow's real commit step (read from promotion.yml, run with bash -e) against a bare origin: partial failure, the same-org case, success, and no changes. Refs TEST-141 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ef619e1 to
c07fed1
Compare
chris-garber-vapi
left a comment
There was a problem hiding this comment.
I'm a little wary on this one. It seems to me like if we're making git blobs for the files that we could instead just be doing some type of commit operation, so we could have like: test -> apply -> commit or something like that, rather than having a kinda intermediate half state that we commit after the fact. I think this would make our system more like terraform (which I think we're kinda taking inspiration from).
Don't wanna block here, I'm far from an expert. I think this will likely get the job done, but I don't know if it's the best option
dhruva-vapi
left a comment
There was a problem hiding this comment.
lgtm, snapshotting the blob after each apply is a nice touch since a later failed transition into the same org can't overwrite what actually reached the platform. tests pass locally too
one non-blocking thing: if appliedPathsRecord fails it only warns, so when a later transition fails we'd commit that org's state without its files, which is the same git vs platform drift this PR is fixing. can we make that fail loudly so someone knows to reconcile that org by hand

Value
V.A.L.U.E. tier: project — PR 9 of 10 for inline simulation PR checks (TEST-141). This is a fix to promotion, independent of the check stack (cut from
main), and the promotion gate (PR 10) depends on it..vapi-state.*.json, but the failed transition'spromotionPlanApplyhas already rewritten tracked files in its target org, sogit pull --rebaserefuses with "You have unstaged changes".promotion.ymlwith two or more transitions (dev → staging → prod), and anyone who adds the promotion gate in PR 10, where a failing gate is exactly a mid-run failure.src/promote-cmd.ts:--applyrun truncatestmp/promotion-applied.txt(gitignored);apply.ts, it records each pathgit status --porcelain -z --untracked-files=all -- resources/<target>reports (new, modified, deleted and renamed files, by current path), with its content at that moment written to git's object store (git hash-object -w;-for a deletion). These are read from git, not the plan, because apply's own pull and push can rewrite files the plan didn't name;promotionCommandRun(args, deps = { childRun })gains a test seam..github/workflows/promotion.yml: on a non-success outcome, the commit step:git update-index --cacheinfo; deletions withgit rm --cached), and commits. Staging the recorded content, not the working tree, means a later failed transition that rewrote the same file can't replace what was applied;git reset --hard HEAD && git clean -fd -- resources, so the failed transition's rewrites are discarded;On success it's unchanged: it commits all of
resources.improvements.md: refactor(engine): extract shared slug + folder helpers into slug-utils #35, RESOLVED.Evidence of value
Red/green in a scratch repo.
a → b → c, andcstarts with a trackedassistants/intake.yml.promote --all --applyruns this branch'spromote-cmdwith a fake child runner. Apply intobsucceeds (writing state and one extra file, as apply's pull can). Apply intocwrites state, then fails.promotion.yml, runs withPROMOTION_OUTCOME=failure.main(69c7e83)error: cannot pull with rebase: You have unstaged changes.exit 128origin/mainbase)chore: record promoted Vapi state [skip promotion].vapi-state.b.json/.c.jsonon origin{}/{}(state lost)uuid-b/uuid-c(both recorded)resources/b/assistants/intake.yml,pulled-by-apply.ymlresources/c/assistants/intake.ymlresources/c/assistants/pulled-by-apply.ymlgit cleantmp/promotion-applied.txtafter the run held exactly the tworesources/b/...paths.Same-org case: red before the fix, green after. Two pipelines promote into the same org in one
--allrun. The first appliesresources/c/assistants/intake.ymlwith a's content, and the second rewrites that file with b's content and then fails. Before the fix, the committed file was b's (the failed rewrite); after it, the commit holds a's, the content that was actually applied.Tests: on top of #68,
npm testgoes from 476 to 484 passing.Testing plan
tests/promote-cmd.test.ts(3 tests, a real temp git repo, injected child runner):--applyrun starts a fresh record, and a plan-only run leaves it alone;tests/promote-cmd.test.tsalso checks that each applied file is recorded with its content at apply time (git cat-fileof the recorded blob, after a later rewrite), and a deletion as-.New
tests/promotion-workflow-commit.test.ts(4 tests) runs the workflow's real commit step, read frompromotion.ymland run withbash -e, against a clone of a bare origin, after a promotion driven throughpromote-cmd:A later edit to the YAML that brings either bug back fails here.
The existing
tests/promotion.test.ts, and test(promotion): pin the exact files promoting an assistant writes to the target #68's golden promotion test, pass unchanged.Not tested:
Stacked on #68.
Refs TEST-141
🤖 Generated with Claude Code