Repository navigation
Commit 3c1e10e
authored
fix(promotion): commit the files of transitions that applied when a later one fails (#65)
## Value
**V.A.L.U.E. tier:** project — PR 9 of 10 for inline simulation PR checks ([TEST-141](https://linear.app/vapi/issue/TEST-141/gitops-run-simulation-suites-against-pr-changes-inline-as-ci-checks)). This is a fix to promotion, independent of the check stack (cut from `main`), and the promotion gate (PR 10) depends on it.
- **Problem:** when a multi-transition promotion fails partway, the "Commit reconciled files and UUID state" step pushes **nothing**: not the UUID state, and not the files of transitions that already reached the platform.
- **Why:** on failure the step stages only `.vapi-state.*.json`, but the failed transition's `promotionPlanApply` has already rewritten tracked files in its target org, so `git pull --rebase` refuses with "You have unstaged changes".
- **Result:** git and the platform disagree, and the next promotion plans from stale files.
- **Who it affects:** teams using `promotion.yml` with 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.
- **What changes:**
- **`src/promote-cmd.ts`:**
- each `--apply` run truncates `tmp/promotion-applied.txt` (gitignored);
- after each successful `apply.ts`, it records each path `git 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:
1. stages the state files plus exactly the recorded blobs (`git update-index --cacheinfo`; deletions with `git 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;
2. runs `git reset --hard HEAD && git clean -fd -- resources`, so the failed transition's rewrites are discarded;
3. then rebases and pushes as before.
On success it's unchanged: it commits all of `resources`.
- **`improvements.md`:** #35, RESOLVED.
## Evidence of value
**Red/green in a scratch repo.**
- **Setup:** a bare origin plus a clone; the pipeline is `a → b → c`, and `c` starts with a tracked `assistants/intake.yml`.
- **The run:** `promote --all --apply` runs this branch's `promote-cmd` with a fake child runner. Apply into `b` succeeds (writing state and one extra file, as apply's pull can). Apply into `c` writes state, then fails.
- **Then:** the workflow's real commit step, extracted from each ref's `promotion.yml`, runs with `PROMOTION_OUTCOME=failure`.
| | `main` (69c7e83) | This branch |
|---|---|---|
| Commit step | `error: cannot pull with rebase: You have unstaged changes.` **exit 128** | pushed, **exit 0** |
| `origin/main` | unchanged (only `base`) | `chore: record promoted Vapi state [skip promotion]` |
| `.vapi-state.b.json` / `.c.json` on origin | `{}` / `{}` (state lost) | `uuid-b` / `uuid-c` (both recorded) |
| `resources/b/assistants/intake.yml`, `pulled-by-apply.yml` | missing | **committed** |
| `resources/c/assistants/intake.yml` | old content | old content (the failed rewrite is discarded) |
| `resources/c/assistants/pulled-by-apply.yml` | — | removed by `git clean` |
`tmp/promotion-applied.txt` after the run held exactly the two `resources/b/...` paths.
**Same-org case: red before the fix, green after.** Two pipelines promote into the same org in one `--all` run. The first applies `resources/c/assistants/intake.yml` with 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 test` goes from 476 to 484 passing.
## Testing plan
- **`tests/promote-cmd.test.ts`** (3 tests, a real temp git repo, injected child runner):
- one transition applies and the next fails: only the applied transition's files are recorded, including a file apply rewrote that the plan didn't name;
- every `--apply` run starts a fresh record, and a plan-only run leaves it alone;
- deleted and renamed files are recorded by their current path.
- **`tests/promote-cmd.test.ts`** also checks that each applied file is recorded with its content at apply time (`git cat-file` of 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 from `promotion.yml` and run with `bash -e`, against a clone of a bare origin, after a promotion driven through `promote-cmd`:
- partial failure: state and the applied transition's files are pushed, and the failed rewrite is not;
- the same-org case above;
- success commits every change;
- no changes pushes nothing.
A later edit to the YAML that brings either bug back fails here.
- The existing `tests/promotion.test.ts`, and #68's golden promotion test, pass unchanged.
- **Not tested:**
- a real GitHub Actions promotion run (the step runs locally under bash, read from the YAML) — manual QA with two non-production orgs before merge.
Stacked on #68.
Refs TEST-141
## After review
- **Dhruva:** if recording an applied transition's files fails, the run now stops with an error naming the org and how to reconcile it (`npm run pull -- <org>`), instead of warning and letting the commit step save that org's state without its files. Test: `promote-cmd.test.ts` breaks git after the first apply and expects the run to stop there.
- **Chris (commit per transition):** replied on the PR. The blob snapshot gives the same guarantee, that what's committed is what reached the platform, without the CLI making git commits mid-run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)5 files changed
Lines changed: 676 additions & 9 deletions
File tree
- .github/workflows
- src
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
102 | 115 | | |
103 | 116 | | |
104 | | - | |
| 117 | + | |
105 | 118 | | |
106 | 119 | | |
107 | 120 | | |
108 | 121 | | |
109 | 122 | | |
110 | 123 | | |
111 | | - | |
112 | 124 | | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
113 | 129 | | |
114 | 130 | | |
115 | 131 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| |||
1822 | 1823 | | |
1823 | 1824 | | |
1824 | 1825 | | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
| 1829 | + | |
| 1830 | + | |
| 1831 | + | |
| 1832 | + | |
| 1833 | + | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
| 1837 | + | |
| 1838 | + | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
| 1845 | + | |
| 1846 | + | |
| 1847 | + | |
| 1848 | + | |
| 1849 | + | |
| 1850 | + | |
| 1851 | + | |
| 1852 | + | |
| 1853 | + | |
| 1854 | + | |
| 1855 | + | |
| 1856 | + | |
| 1857 | + | |
| 1858 | + | |
| 1859 | + | |
| 1860 | + | |
| 1861 | + | |
| 1862 | + | |
| 1863 | + | |
| 1864 | + | |
| 1865 | + | |
| 1866 | + | |
| 1867 | + | |
| 1868 | + | |
| 1869 | + | |
| 1870 | + | |
| 1871 | + | |
| 1872 | + | |
| 1873 | + | |
| 1874 | + | |
| 1875 | + | |
| 1876 | + | |
| 1877 | + | |
| 1878 | + | |
| 1879 | + | |
| 1880 | + | |
| 1881 | + | |
| 1882 | + | |
| 1883 | + | |
| 1884 | + | |
| 1885 | + | |
| 1886 | + | |
| 1887 | + | |
1825 | 1888 | | |
1826 | 1889 | | |
1827 | 1890 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
34 | 47 | | |
35 | 48 | | |
36 | 49 | | |
| |||
126 | 139 | | |
127 | 140 | | |
128 | 141 | | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
129 | 203 | | |
130 | 204 | | |
131 | 205 | | |
| |||
141 | 215 | | |
142 | 216 | | |
143 | 217 | | |
| 218 | + | |
144 | 219 | | |
| 220 | + | |
145 | 221 | | |
146 | | - | |
| 222 | + | |
147 | 223 | | |
148 | 224 | | |
149 | 225 | | |
150 | 226 | | |
151 | 227 | | |
152 | | - | |
| 228 | + | |
153 | 229 | | |
154 | 230 | | |
155 | 231 | | |
| |||
177 | 253 | | |
178 | 254 | | |
179 | 255 | | |
180 | | - | |
| 256 | + | |
181 | 257 | | |
182 | 258 | | |
183 | 259 | | |
184 | 260 | | |
185 | 261 | | |
| 262 | + | |
186 | 263 | | |
187 | 264 | | |
188 | 265 | | |
189 | 266 | | |
190 | 267 | | |
| 268 | + | |
191 | 269 | | |
192 | 270 | | |
193 | 271 | | |
| |||
198 | 276 | | |
199 | 277 | | |
200 | 278 | | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
201 | 284 | | |
202 | 285 | | |
203 | 286 | | |
| |||
209 | 292 | | |
210 | 293 | | |
211 | 294 | | |
| 295 | + | |
212 | 296 | | |
213 | 297 | | |
214 | 298 | | |
| |||
0 commit comments