Skip to content

Commit e84566d

Browse files
committed
fix(drift): enhance drift classification logic to handle local and platform agreement
This commit updates the `classifyDrift` function to treat cases where the local hash matches the platform hash as "clean," regardless of the last pulled hash. This change addresses potential issues with stale baselines and improves the efficiency of the reconciliation process. Additionally, the pull logic is refined to ensure that local and platform agreement is consistently recognized, enhancing overall synchronization reliability. The output messages during resource synchronization are also clarified to reflect these changes.
1 parent 897c3ec commit e84566d

3 files changed

Lines changed: 20 additions & 20 deletions

File tree

src/drift.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export interface ClassifyDriftInput {
4242
export function classifyDrift(input: ClassifyDriftInput): DriftDirection {
4343
const { localHash, lastPulledHash, platformHash } = input;
4444
if (!lastPulledHash) return "no-baseline";
45+
// INVARIANT: live local + platform agreement is NEVER drift, whatever the
46+
// (possibly stale) baseline says. A stale baseline happens legitimately —
47+
// e.g. a push made the dashboard match local while the baseline still held
48+
// the previous pull's hash. Callers treat `clean` as "refresh the baseline
49+
// and move on", which self-heals the stale pointer.
50+
if (localHash === platformHash) return "clean";
4551
const localMatches = localHash === lastPulledHash;
4652
const platformMatches = platformHash === lastPulledHash;
4753
if (localMatches && platformMatches) return "clean";
@@ -151,12 +157,11 @@ export async function checkDriftForUpdate(options: {
151157
hashLocalResource(resourceType, resourceId) ?? baseline;
152158

153159
// Local and platform are byte-identical → there is nothing to reconcile and
154-
// the PATCH is a no-op. NEVER block here, even if `lastPulledHash` disagrees
160+
// the PATCH is a no-op. NEVER block here, even if the baseline disagrees
155161
// with both (a stale or older-basis baseline must not manufacture a conflict
156-
// when the two LIVE sides already agree). `classifyDrift` still reports this
157-
// as `both-diverged` by its descriptive contract — that signals the stale
158-
// state pointer — but the push *gate* treats agreement between local and
159-
// platform as clean. This is the fix for the phantom-drift class: a freshly
162+
// when the two LIVE sides already agree). `classifyDrift` encodes the same
163+
// invariant, but this early return also short-circuits before the direction
164+
// bookkeeping. This is the fix for the phantom-drift class: a freshly
160165
// upgraded customer repo carries baselines written in an older hash basis,
161166
// so every untouched resource hit `both-diverged` on its first push.
162167
if (localHash === platformHash) {

src/pull.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -661,20 +661,15 @@ export async function pullResourceType(
661661
const platformHash = hashPayload(
662662
canonicalizeForHash(resource, state, credReverse),
663663
);
664-
// Local and platform already agreeing means there is nothing to
665-
// reconcile, whatever the (possibly stale) baseline says — treat as
666-
// clean and fall through to the write path: the rewrite is a
667-
// content no-op that re-seeds the baseline from the disk form,
668-
// self-healing the stale pointer. Mirrors the same guard in
669-
// drift.ts's push gate.
670-
const direction =
671-
localHash === platformHash
672-
? "clean"
673-
: classifyDrift({
674-
localHash,
675-
lastPulledHash: baseline,
676-
platformHash,
677-
});
664+
// Note: classifyDrift treats live local + platform agreement as
665+
// `clean` whatever the (possibly stale) baseline says — the clean
666+
// fall-through to the write path re-seeds the baseline from the
667+
// disk form, self-healing the stale pointer.
668+
const direction = classifyDrift({
669+
localHash,
670+
lastPulledHash: baseline,
671+
platformHash,
672+
});
678673
if (driftCounts) driftCounts[direction]++;
679674

680675
// The drift baseline now lives in the hash store, NOT in the state

src/push.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,7 @@ async function main(): Promise<void> {
13431343
console.log(`🚀 Vapi GitOps Apply - Environment: ${VAPI_ENV}`);
13441344
console.log(` API: ${VAPI_BASE_URL}`);
13451345
console.log(
1346-
` Deletions: ${FORCE_DELETE ? "⚠️ ENABLED (--force)" : "🔒 Disabled (dry-run)"}`,
1346+
` Deletions: ${FORCE_DELETE ? "⚠️ ENABLED (--force)" : "🔒 Disabled (pass --force to enable)"}`,
13471347
);
13481348
if (DRY_RUN) {
13491349
console.log(" Mode: 🧪 DRY-RUN (no API mutations, no state file write)");

0 commit comments

Comments
 (0)