feat(sync): trust cluster -- pull --theirs, manifest<->disk invariant, isDisabled round-trip, never-fetched guard (#466 #467 #472 #497) - #506
Merged
Conversation
…, isDisabled round-trip, never-fetched guard, adopted-by-id writeback (#466, #467, #472, #497) Four reliability fixes that make kbagent sync safe against production trees edited by other people, released together as 0.72.0: - sync pull --theirs (#466): supported 'discard local, take production' reconcile path. Overwrites locally-modified configs and rows, restores deleted/missing files, resolves true merge conflicts by taking remote instead of aborting. SYNC_CONFLICT error now points at it. - manifest<->disk invariant (#466/#472): pull re-materializes a tracked config whose local dir was deleted (previously silent 'Already up to date'), and can no longer register a manifest entry without writing its files (the remote_unchanged branch now requires the file to exist, matching the row-level guard that already did). - never-fetched guard (#472): a manifest entry with an empty pull_hash and no local files (phantom left by a pre-0.72 name-collision pull) is excluded from delete planning -- push --force can no longer DELETE a remote config that was never fetched. diff/push/status surface it as never_fetched with a warning; the next pull materializes it. Local deletion of a properly-pulled config still deletes on push. - isDisabled round-trip (#467): pull writes sparse 'is_disabled: true' into _config.yml (absent = enabled, so existing trees do not mass-diff), diff surfaces enabled/disabled drift, push sends the state when the key is present (absent leaves remote untouched). Config + row level; create paths (config new --push, sync clone) honor it too. - adopted-by-id writeback (#497): pushing an untracked file whose _keboola.config_id resolves on the branch now writes the manifest entry with fresh hashes, so follow-up diffs are stable and a later local deletion is detected. - sync status all-clear now says it is a LOCAL-only check and points at sync diff for remote drift; row-only pull writes no longer report 'Already up to date'. All behaviors live-verified against project 5946 (us-east4.gcp): phantom push guard, deleted-dir refetch, --theirs conflict resolution (config + row), isDisabled push/pull/drift, adopted-by-id manifest writeback, and the preserved GitOps delete flow. 32 new unit tests + 1 new live E2E test (test_sync_theirs_reconcile_and_is_disabled); full suite 4400 passed.
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four sync-engine reliability fixes ("sync trust cluster"), shipped together as 0.72.0. They close the failure modes reported by production users maintaining ~20 projects via
kbagent syncwhere production is also edited directly by other people:Closes #466, closes #467, closes #472, closes #497.
1.
sync pull --theirs— supported reconcile path (#466)The reported workaround for a drifted tree was hand-editing
.keboola/manifest.json.--theirsreplaces that: remote wins everywhere — overwrites locally-modified configs and rows, restores deleted/missing files, and resolves true merge conflicts by taking the remote version instead of aborting. Works standalone (no--forceneeded); theSYNC_CONFLICTerror message now points at it.2. Manifest⇔disk invariant (#466 pt3 + #472 pull side)
sync pullnow re-materializes a tracked config whose local dir was deleted, even when the remote is unchanged. Previously it reportedAlready up to dateand the (CLAUDE.md-documented!) delete-dir-then-pull workaround silently did nothing.remote_unchangedfast path now requires the local_config.ymlto exist — the exact guard the row-level branch already had. This kills the name-collision phantom (pull_hash: "", no dir) at the source.3. Never-fetched guard (#472 push side)
Reproduced on v0.71.0: a phantom manifest entry (empty
pull_hash, no dir — left by a pre-0.72 name-collision pull) madesync push --force --dry-runplanDELETEof a remote config nobody ever deleted. Now such entries are excluded from delete planning, reported asnever_fetched(JSON key + human warning on diff/push/status), and healed by the next pull. Deleting a properly-pulled config locally still deletes on push — the GitOps delete flow is unchanged (covered by a contrast test + verified live).4. Config-level
isDisabledround-trip (#467)The components list API returns
isDisabled, but the serializer never read it — a flow disabled in production was byte-identical to an enabled one locally, andsync diffreported "in sync". Now:is_disabled: trueinto_config.yml(absent = enabled, so existing trees do not mass-diff after upgrade; hashing treats absent == false at the root only),sync diffsurfaces enabled/disabled drift in both directions,is_disabled: falsere-enables; absent key leaves remote untouched), for configs and rows;config new --push/sync clonecreate disabled configs when the local file says so.5. Adopted-by-id manifest writeback (#497)
Pushing an untracked file whose
_keboola.config_idresolves on the branch (the #482 adopt-update path) now also writes the manifest entry with fresh hashes, so the next diff reads a stable entry and a later local deletion is detected.6. UX honesty fixes
sync statusall-clear now saysNo local changes detected ... Local check only -- run 'kbagent sync diff'— status never contacts the API, and treating it as a drift audit was the kbagent sync: cannot reliably reconcile a drifted tree with production;sync diff/statusreport false "in sync" #466 trap.--theirsrestoring an edited row) no longer reportAlready up to date.Verification
us-east4.gcp), driven through the CLI only: phantom push guard (before: planned DELETE; after: warning + no delete), deleted-dir refetch,--theirsconflict resolution (config + row),isDisabledpush→pull→drift round-trip verified viaconfig detail, adopted-by-id writeback (manifest entry restored, follow-up diff clean),--all-projects --theirs, and the preserved GitOps delete flow (twins cleaned up via delete-dir + push).tests/test_sync_reconcile.py+ additions to config-format/diff-engine/CLI/client tests), 1 new live E2E test (test_sync_theirs_reconcile_and_is_disabledintests/test_e2e.py, runs undermake test-e2e).ruff/ruff format/tyclean;make changelog-check,skill-check,command-sync-check,version-checkgreen.context.pyAGENT_CONTEXT,commands-reference.md,gotchas.md(taggedsince v0.72.0),sync-workflow.md(new "Reconciling a drifted tree" section),keboola-expert.md(byte budget respected).Compatibility notes
is_disabledhashing treats absence as false, and only genuinely disabled configs surface (asremote_modified— pull to adopt the field).