Skip to content

Commit e201be2

Browse files
committed
feat(pull): add granular drift resolution
1 parent 57e66c6 commit e201be2

9 files changed

Lines changed: 625 additions & 38 deletions

File tree

AGENTS.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,32 @@ Runs the engine's local validators against every YAML/MD file in the org without
9999
- End-of-pull summary with counts per direction
100100
- A hard gate (`--resolve` flag) on 3-way conflicts before they silently lose data
101101

102+
When multiple resources are `both-diverged`, you can mix decisions in one pull:
103+
104+
```bash
105+
# Global mode for every both-diverged resource.
106+
npm run pull -- <org> --resolve=ours
107+
npm run pull -- <org> --resolve=theirs
108+
npm run pull -- <org> --resolve=fail
109+
110+
# Per-resource modes in one command.
111+
npm run pull -- <org> \
112+
--resolve=assistants/intake=ours \
113+
--resolve=squads/main=theirs
114+
115+
# Path-level override: choose a resource base, then override selected paths.
116+
# This keeps the git copy of the assistant except for dashboard voice settings.
117+
npm run pull -- <org> \
118+
--resolve=assistants/intake=ours \
119+
--resolve-path=assistants/intake:voice=theirs
120+
```
121+
122+
Path rules use dot paths, with numeric array indexes supported either as
123+
`members.0.assistantId` or `members[0].assistantId`. Assistant markdown bodies
124+
map to `model.messages` because pull parses `.md` resources into the same
125+
object shape used for hashing. A path-level rule requires a per-resource or
126+
global `ours` / `theirs` base so unspecified paths have an explicit owner.
127+
102128
`--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.
103129

104130
**Pull-output icon legend.** Distinct semantics in a single pulled-resource line:
@@ -110,6 +136,7 @@ Runs the engine's local validators against every YAML/MD file in the org without
110136
| `✏️` | Locally modified file detected by git, preserved as-is |
111137
| `⬆️` | `local-ahead` — local has unpushed edits, needs to flow UP to dashboard (preserved) |
112138
| `⬇️` | `--resolve=theirs` — overwrote local with dashboard (flowed DOWN) |
139+
| `🔀` | Mixed path resolution — one resource merged dashboard and git-selected paths |
113140
| `🔒` | Platform-default resource (read-only, immutable) |
114141
| `🚫` | Matched `.vapi-ignore` (not tracked locally) |
115142
| `🗑️` | Locally deleted (deletion intent recorded in state) |
@@ -992,4 +1019,3 @@ When transferring to human:
9921019
3. Create simulations (pair personality + scenario)
9931020
4. Create suites (batch simulations together)
9941021
5. Run via Vapi dashboard or API
995-

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,29 @@ npm run pull -- <org> --bootstrap
224224
npm run pull -- <org> --type assistants --id <uuid>
225225
```
226226

227+
When `pull` reports `both-diverged`, choose a whole-run default, mix decisions
228+
per resource, or merge selected paths:
229+
230+
```bash
231+
# Same decision for every both-diverged resource.
232+
npm run pull -- <org> --resolve=ours
233+
npm run pull -- <org> --resolve=theirs
234+
235+
# Different decisions in one run.
236+
npm run pull -- <org> \
237+
--resolve=assistants/intake=ours \
238+
--resolve=squads/main=theirs
239+
240+
# Keep git as the base, but take dashboard voice settings.
241+
npm run pull -- <org> \
242+
--resolve=assistants/intake=ours \
243+
--resolve-path=assistants/intake:voice=theirs
244+
```
245+
246+
Path rules use the parsed resource object shape. For assistants, the Markdown
247+
body is represented as the system message under `model.messages`; squad arrays
248+
can be addressed with indexes such as `members[0].assistantId`.
249+
227250
### Live testing what you just deployed
228251

229252
```bash

improvements.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ you which stack PR closes the row.**
7373
| 19 | No `maxTokens` floor warning for tool-using assistants | `maxTokens: 1` bricks the assistant silently | None | RESOLVED 2026-04-30 (Stack D) |
7474
| 20 | Prompt vocabulary leaks into TTS | `Reason.` becomes verbal contaminant | None | Partial — Stack D heuristic |
7575
| 21 | `.vapi-ignore` was pull-only (push could silently delete) | `--force` push DELETEd dashboard-only opt-outs | None | RESOLVED 2026-05-11 (#TBD) |
76+
| 22 | Granular `both-diverged` resolution | Mix dashboard/git choices without manual hand-merge | #4 | RESOLVED 2026-05-29 (#TBD) |
7677

7778
---
7879

@@ -1030,6 +1031,58 @@ RESOLVED 2026-05-11 (#TBD — PR number updates when opened).
10301031

10311032
---
10321033

1034+
## 22. Granular `both-diverged` resolution was whole-run only
1035+
1036+
**[RESOLVED 2026-05-29] (#TBD)**
1037+
1038+
**Discovered:** PRISM-852, from the mudflap-prod iForm drift incident on
1039+
2026-05-26. Squad and assistant resources diverged after dashboard pushes
1040+
and local bucket edits, and the operator had to use coarse overwrite/manual
1041+
merge choices to ship.
1042+
1043+
### Problem
1044+
1045+
`pull` could classify resources as `both-diverged`, but conflict resolution
1046+
was whole-run and whole-resource only. Operators could not take dashboard
1047+
`voice` while keeping git `model.messages`, or resolve one squad from the
1048+
dashboard and another assistant from git in a single command.
1049+
1050+
### Current behavior (Verified)
1051+
1052+
- Whole-run defaults still work: `--resolve=ours|theirs|fail`.
1053+
- Per-resource resolution is supported with repeatable flags:
1054+
`--resolve=assistants/intake=ours --resolve=squads/main=theirs`.
1055+
- Path-level resolution is supported by choosing a resource base and then
1056+
overriding parsed object paths:
1057+
`--resolve=assistants/intake=ours --resolve-path=assistants/intake:voice=theirs`.
1058+
- Path rules support dot paths and numeric array indexes, including
1059+
`members[0].assistantId`. Assistant Markdown bodies are parsed into
1060+
`model.messages`, so prompt/body resolution uses the same object shape as
1061+
the hash pipeline.
1062+
- Path-level mixed writes set `lastPulledHash` to the platform hash observed
1063+
during resolution. If the merged local file still differs from the full
1064+
dashboard payload, the next pull classifies it as `local-ahead`, not a
1065+
phantom `both-diverged`.
1066+
1067+
### Risk
1068+
1069+
Without granular resolution, a safe sync can force broad choices: lose
1070+
dashboard changes, lose git changes, or hand-merge resource files manually
1071+
while preserving the engine's hash invariants by memory.
1072+
1073+
### Resolution
1074+
1075+
Added a pure drift-resolution parser/merger and wired it into `pull` after
1076+
all `both-diverged` resources are collected, before any conflict writes occur.
1077+
If any conflict lacks an explicit mode, the pull exits before applying scoped
1078+
resolutions. Test coverage lives in `tests/drift-resolve.test.ts`.
1079+
1080+
### Status
1081+
1082+
RESOLVED 2026-05-29 (#TBD — PR number updates when opened).
1083+
1084+
---
1085+
10331086
## Out of scope (intentionally not improvements)
10341087

10351088
- **State file is identity-only and not git-ignored.** It's intentionally

0 commit comments

Comments
 (0)