You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: enhance documentation for sync behavior and migration process
This commit updates the documentation to include a new section on sync behavior, detailing the scenarios for pull, push, and apply operations. It also clarifies the migration process for legacy state files to the new hash store format. Key additions include:
- A comprehensive matrix in `docs/learnings/sync-behavior.md` outlining the behavior of the sync engine under various conditions.
- Updates to `AGENTS.md`, `CLAUDE.md`, and `README.md` to reference the new sync behavior documentation and migration command.
- Enhanced clarity on the purpose and usage of the `npm run migrate` command, emphasizing its role in transitioning to the hash-store engine.
These changes improve user understanding of the system's behavior and facilitate smoother transitions for developers working with the Vapi platform.
| What pull/push/apply do in every drift & existence scenario |`docs/learnings/sync-behavior.md`|
37
38
38
39
**Where new knowledge goes:**
39
40
@@ -71,6 +72,7 @@ If you're unsure where something goes, default to `docs/learnings/`. The README
71
72
| Push with new resources |`npm run push -- <org> --allow-new-files` — bypass orphan-YAML gate. **AI agents**: do NOT auto-pass this flag; confirm with the human first (see push section below) |
72
73
| Test a call |`npm run call -- <org> -a <assistant-name>` or `-s <squad-name>`|
73
74
| Run a simulation suite |`npm run sim -- <org> --suite <name> --target <assistant-name>`|
75
+
| Migrate a legacy state file |`npm run migrate` — one-shot, all orgs; required once after upgrading to the hash-store engine |
74
76
75
77
---
76
78
@@ -82,6 +84,8 @@ Three commands deploy changes to the Vapi platform. Pick the safest one that fit
82
84
83
85
Pulls the platform's current state, merges with your local files, then pushes the merged result. This protects you from racing dashboard edits made between your last pull and your push. Use this for ~99% of deployments.
84
86
87
+
Conflict handling is **per resource, never umbrella**: apply defaults to `--resolve=defer`, so the pull stage preserves local files and the drift baselines for genuinely conflicted resources, and the push stage then asks one interactive question per conflicted resource (push mine / keep dashboard / save a `.bkp` copy for manual merge). Clean and one-sided changes flow silently in their obvious direction. The full scenario matrix lives in `docs/learnings/sync-behavior.md`. Explicit `--resolve=ours|theirs|fail` keep non-interactive (CI) semantics.
88
+
85
89
```bash
86
90
npm run validate -- <org># schema check first, no network call
87
91
npm run apply -- <org># full-org apply
@@ -94,10 +98,12 @@ Runs the engine's local validators against every YAML/MD file in the org without
94
98
95
99
### `npm run pull -- <org>` — refresh from dashboard (default: plain pull)
96
100
97
-
**Default to plain pull, NOT --force.** Plain `npm run pull -- <org>` shows:
- A hard gate (`--resolve` flag) on 3-way conflicts before they silently lose data
101
+
**Default to plain pull, NOT --force.** Plain `npm run pull -- <org>`:
102
+
- Syncs one-sided changes in their obvious direction: `dashboard-ahead` (local unchanged, dashboard edited) is written down ⬇️; `local-ahead` (you edited, dashboard unchanged) is preserved ⬆️ for the next push
103
+
- Prints an end-of-pull summary with counts per drift direction
104
+
- Hard-gates true 3-way conflicts behind `--resolve=ours|theirs|fail|defer` before they silently lose data (`defer` leaves the conflict for push's per-resource prompt — apply's default)
105
+
106
+
Drift direction is computed against the per-developer baseline store `.vapi-state-hash/<org>/<uuid>` (gitignored) — the hash of the last platform content *you* pulled or pushed. The committed state file holds only `name → uuid`. See `docs/learnings/sync-behavior.md` for every scenario.
101
107
102
108
`--force` skips all of this and just overwrites local with dashboard. Use it ONLY when you literally need to nuke local and re-materialize dashboard truth (rare). Plain pull is the DEFAULT for both humans and agents; `--force` is the escape hatch.
103
109
@@ -107,25 +113,35 @@ Runs the engine's local validators against every YAML/MD file in the org without
107
113
|------|---------|
108
114
|`📝`| Engine wrote/updated a file on disk (clean / no-baseline path) |
109
115
|`✨`| Engine created a NEW file on disk (first-time pull of this resource) |
110
-
|`✏️`| Locally modified file detected by git, preserved as-is |
|`🚫`| Matched `.vapi-ignore` (not tracked locally), or a `.bkp` backup copy refused as a resource|
115
122
|`🗑️`| Locally deleted (deletion intent recorded in state) |
116
123
117
-
Mental model: `⬆️`flows UP (push), `⬇️` flows DOWN (pull-overwrite), `📝` is the engine doing routine file I/O.
124
+
Push adds two more: `⏭️`(conflict prompt → kept dashboard, push skipped) and `📄` (conflict prompt → dashboard copy saved as `<name>.<TIMESTAMP>.bkp.<ext>` for manual merge).
118
125
119
-
**First-adoption noise on customer repos.** The first `npm run audit -- <org>` after upgrading to a version with the content-drift rule will surface `[content-drift] [no-baseline]` info findings for every resource whose state row predates `lastPulledHash` tracking. These are info-severity (do NOT block CI), but the noise is real. Two ways to clear:
120
-
-`npm run pull -- <org> --bootstrap` — refreshes state with the current platform-hash baseline, no resource materialization. One-shot fix for the whole fleet.
121
-
- Or just let them sit — each plain `npm run pull` after this writes `lastPulledHash` for whichever resources it touches, so the no-baseline rows naturally drain over the next few sync cycles.
126
+
Mental model: `⬆️` flows UP (push), `⬇️` flows DOWN (pull), `📝`is the engine doing routine file I/O.
127
+
128
+
**One-time migration on engine upgrade.** Repos that predate the hash-store engine carry `lastPulledHash`/`lastPushedHash`/timestamps inside `.vapi-state.<org>.json`. `pull`, `push`, and `apply` refuse to run on that legacy shape — run `npm run migrate` once (no org argument, no token needed): it slims every state file to pure `name → uuid` and seeds each org's `.vapi-state-hash/` baselines from the legacy hashes, so drift detection keeps working without a re-pull. Idempotent. Fresh clones / new developers have no baselines yet (the store is per-dev and gitignored) — run a plain `pull` first to seed them; until then drift checks log a no-baseline warning and proceed.
122
129
123
130
### `npm run push -- <org>` — raw push, no pre-pull
124
131
125
132
Skips the merge pass. Only use when (a) you literally just ran `pull` and (b) you're certain no one has touched the dashboard since. In a multi-developer environment or when dashboard editors are in play, default to `apply` instead. Stale local state can clobber recent dashboard edits or PATCH against UUIDs that no longer exist.
126
133
127
134
If you do use `push`, dry-run first: `npm run push -- <org> --dry-run`.
128
135
136
+
#### Per-resource drift gate (before every PATCH)
137
+
138
+
Before updating a resource, push GETs its current dashboard payload, hashes it, and compares against the stored baseline (`.vapi-state-hash/<org>/<uuid>`):
139
+
140
+
-**Hashes match** → your local edit is the natural next step in the change chain → pushed silently, and the baseline is refreshed from the PATCH **response** (what the platform actually stored).
141
+
-**Hashes differ** → someone else published changes since your last sync. In a terminal, push asks **for that resource only**: ① push my local version (take ownership) ② keep the dashboard version (skip, local untouched) ③ save the dashboard version as `<name>.<TIMESTAMP>.bkp.<ext>` beside your file and skip, for a manual merge. In CI / piped runs the resource is blocked instead (use `--overwrite` to push unconditionally).
142
+
143
+
Backup copies (`*.bkp.*`, gitignored) are merge reference material only — invisible to the loader, the orphan gate, audit, the interactive picker, and explicit CLI paths.
`push` refuses by default when any local YAML file lacks a corresponding entry in `.vapi-state.<org>.json`. The engine can't disambiguate "intentionally new resource" vs "rename of an existing resource" vs "stale cruft" from the file alone. Silently treating every orphan as a create has been the spawn-source for duplicate-resource cascades on customer dashboards.
@@ -157,7 +173,7 @@ The same gate fires inside `apply` (which runs pull → merge → push). If pull
157
173
4. Verify with `npm run call -- <org> -a <name>` and/or `npm run sim -- <org> --suite <name> --target <name>`
158
174
5. If something looks wrong: `npm run rollback -- <org> --list` → `--to <timestamp>`
159
175
160
-
**Why this matters:** the gitops engine tracks resource UUIDs in `.vapi-state.<org>.json`. Bare `push` trusts that file is current. If the dashboard has changed since your last pull — someone edited an assistant in the UI, a teammate ran a different push, a structured output got linked via another path — your local state can be stale by minutes. `apply` refreshes state before mutating, eliminating that entire class of race.
176
+
**Why this matters:** the gitops engine tracks resource UUIDs in `.vapi-state.<org>.json` (pure `name → uuid`) and the last-seen platform content hash per resource in the per-developer `.vapi-state-hash/<org>/<uuid>` store. The pre-PATCH drift gate compares that baseline against the live dashboard, so out-of-band edits — someone edited an assistant in the UI, a teammate ran a different push, a structured output got linked via another path — are detected per resource and surfaced as a per-resource question instead of being silently clobbered. `apply`additionally refreshes local files before mutating, eliminating the stale-state race entirely.
- Pull/push/apply behavior per drift & existence scenario → `docs/learnings/sync-behavior.md`
68
69
69
70
This list mirrors the "Learnings & recipes" table in `AGENTS.md`. Keep both in sync — if you add a new learnings file, update both files plus `docs/learnings/README.md`.
Copy file name to clipboardExpand all lines: README.md
+15-5Lines changed: 15 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -80,6 +80,7 @@ Every command works in two modes:
80
80
|`npm run rollback`| — |`npm run rollback -- <org> --list` or `--to <ISO>`| Restore from a snapshot in `.vapi-state.<org>.snapshots/` (one is written before every push/apply). |
81
81
|`npm run call`| ✅ |`npm run call -- <org> -a <name>` or `-s <squad>`| Start an interactive WebSocket call against an assistant or squad. |
82
82
|`npm run sim`| — |`npm run sim -- <org> --suite <name> --target <name>`| Run a simulation suite (or specific simulations) against a deployed assistant/squad. |
83
+
|`npm run migrate`| — |`npm run migrate`| One-time, all orgs at once: slim legacy state files to pure `name → uuid` and seed the per-developer `.vapi-state-hash/` baseline store from the old hashes. Required once after upgrading to the hash-store engine — `pull`/`push`/`apply` refuse legacy-shaped state until it runs. Idempotent. |
83
84
|`npm run build`| — | — | Type-check the codebase (`tsc --noEmit`). |
0 commit comments