Commit 3075218
authored
feat(push): orphan-YAML gate — halt push when local files lack state entries (#30)
* feat(push): orphan-YAML gate — halt push when local files lack state entries
Catches the duplicate-creation patterns surfaced during the gitops-mudflap
working session 2026-05-13:
- Flow F: user renames a file locally (`mv foo.md bar.md`) then pushes. bar.md
has no state entry → engine silently POSTed as a new resource, leaving the
original foo's UUID orphaned on the dashboard.
- Flow G: dashboard rename → pull writes a new <name>-<uuid8>.md file but
leaves the old YAML on disk. Next push iterates the old YAML, POSTs it,
creates a duplicate.
- Flow M: `npm run apply` (pull → merge → push) compresses Flow G into one
click, silently spawning duplicates.
The gate is default-on. When it fires, push exits 1 with a verbose error
that lists every orphan, pairs orphans with possible state-only rename
sources by shared base-slug, and includes an AI-agent-addressed paragraph
explicitly asking agents NOT to auto-pass --allow-new-files without
confirming with the human.
ANSI color when stderr is a TTY; plain text when piped/captured (CI, AI
agent stdout capture).
Override: --allow-new-files flag bypasses the gate with a one-line notice.
Apply propagates the flag through `pushArgs`.
Bootstrap mode (empty state) suppresses the gate — genuine first-time
pushes are all "new".
Selective push (`-- <paths>`) scopes the gate to the selection. Unrelated
orphans elsewhere don't block.
Reuses audit.ts's `checkOrphanYaml` detection via a shared
`findOrphanResourceIds` helper exported from new-file-gate.ts — single
source of truth for "what counts as an orphan YAML".
Suite: 188/188 tests pass (21 new for this gate).
* fix(new-file-gate): respect .vapi-ignore + use real env in paths + dry-run verb
Addresses three code-review findings on the new-file-gate PR:
1. **M1 — .vapi-ignore now suppresses the gate** for files the customer
has explicitly opted out of gitops tracking. Without this, the gate
would halt every push for stale dashboard artifacts that customers
keep around (silenced via .vapi-ignore) — defeating both the
.vapi-ignore workaround and the gate's intent of catching REAL
rename / orphan cases. Same `matchesIgnore` helper that audit.ts
uses; injected via the gate's DI options for filesystem-free tests.
2. **M2 — `<org>` placeholder in relativePath replaced with real
`VAPI_ENV`**. Previously the gate's message rendered
`resources/<org>/...` literally. Now renders e.g.
`resources/mudflap-prod/...`. Tests updated to use `[^/]+` regex
class so they work regardless of the test env name.
3. **M2 — asymmetric `filter.endsWith(relativePath)` direction
removed** from `pathMatchesAnyFilter`. The remaining checks
(exact match, suffix match, resourceId-with-extension tail match)
cover the real cases without over-matching when relativePath is
short.
Cleanups:
- Dead UUID-length conditional in formatGateMessage (real UUIDs are
always 36 chars, slice always fires) → unconditional slice.
- Duplicate `(possible rename SOURCES):` header in both branches of
the rename-sources block → print header once, then either '(none)'
or entries.
- Dry-run-aware verb in the bypass notice: "would create" vs
"creating" based on DRY_RUN flag.
New test: pin .vapi-ignore suppression as a regression guard against
re-introducing the M1 bug. 189/189 tests pass.1 parent 7e051fa commit 3075218
6 files changed
Lines changed: 1011 additions & 20 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
45 | 57 | | |
46 | | - | |
47 | 58 | | |
48 | 59 | | |
49 | 60 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
179 | 180 | | |
180 | 181 | | |
181 | 182 | | |
182 | | - | |
183 | | - | |
184 | | - | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | | - | |
193 | | - | |
194 | | - | |
195 | | - | |
196 | | - | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
47 | 50 | | |
48 | 51 | | |
49 | 52 | | |
| |||
88 | 91 | | |
89 | 92 | | |
90 | 93 | | |
| 94 | + | |
91 | 95 | | |
92 | 96 | | |
93 | 97 | | |
| |||
97 | 101 | | |
98 | 102 | | |
99 | 103 | | |
| 104 | + | |
100 | 105 | | |
101 | 106 | | |
102 | 107 | | |
103 | 108 | | |
104 | 109 | | |
105 | 110 | | |
106 | 111 | | |
| 112 | + | |
107 | 113 | | |
108 | 114 | | |
109 | 115 | | |
| |||
119 | 125 | | |
120 | 126 | | |
121 | 127 | | |
122 | | - | |
| 128 | + | |
| 129 | + | |
123 | 130 | | |
124 | 131 | | |
125 | 132 | | |
| |||
184 | 191 | | |
185 | 192 | | |
186 | 193 | | |
187 | | - | |
| 194 | + | |
188 | 195 | | |
189 | 196 | | |
190 | 197 | | |
| |||
257 | 264 | | |
258 | 265 | | |
259 | 266 | | |
| 267 | + | |
260 | 268 | | |
261 | 269 | | |
262 | 270 | | |
| |||
0 commit comments