Skip to content

sync: clear sync state on clean sync#934

Closed
sharvilshah wants to merge 6 commits into
mainfrom
sbs/snt-357-clear-sync-state-on-clean-sync
Closed

sync: clear sync state on clean sync#934
sharvilshah wants to merge 6 commits into
mainfrom
sbs/snt-357-clear-sync-state-on-clean-sync

Conversation

@sharvilshah
Copy link
Copy Markdown
Contributor

Fixes SNT-357

@sharvilshah sharvilshah requested a review from a team as a code owner May 5, 2026 16:03
@sharvilshah sharvilshah marked this pull request as draft May 5, 2026 16:04
@github-actions github-actions Bot added configurator Issues or PRs related to the configurator / Santa configuration comp/santad Issues or PRs related to the daemon comp/gui Issues or PRs related to the Santa GUI comp/santasyncservice Issues or PRs related to the sync protocol comp/santactl Issues or PRs related to santactl lang/objc++ PRs modifying files in ObjC++ lang/swift PRs modifying files in Swift comp/common size/l Size: large labels May 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a clearSyncStateBeforeApply flag to SNTConfigBundle, implements atomic bundle application and main-thread clearing of in-memory and on-disk sync state, updates XPC docs and daemon wiring to use the atomic path for clean syncs, and adds tests and BUILD updates covering these behaviors.

Changes

Sync State Cleanup Refactoring

Layer / File(s) Summary
Data Shape / Bundle
Source/common/SNTConfigBundle.h, Source/common/SNTConfigBundle.mm, Source/common/SNTConfigBundleTest.mm
Adds clearSyncStateBeforeApply accessor and backing field, updates encode/decode, and extends tests for accessor and secure-coding round-trips.
Configurator Interface & Tests
Source/common/SNTConfigurator.h, Source/common/SNTConfigurator.mm, Source/common/SNTConfiguratorTest.mm, Source/common/BUILD
SNTConfigurator imports SNTConfigBundle.h; public clear-sync documentation replaced with atomicallyApplyBundle:; private clearSyncState now resets in-memory syncState and deletes the on-disk plist on the main thread; tests add bundle test category, helper, and clear/atomic-apply tests; BUILD deps updated.
XPC & Daemon Wiring
Source/common/SNTXPCControlInterface.h, Source/santad/SNTDaemonControlController.mm
XPC protocol docs updated to describe merge vs atomic-swap apply; removed updateSyncSettings:reply: declaration; daemon update path checks clearSyncStateBeforeApply and takes an atomic-replace path that applies bundle atomically and flushes CEL caches if needed.
Sync Service Postflight & Manager Docs
Source/santasyncservice/SNTSyncConfigBundle.mm, Source/santasyncservice/SNTSyncManager.h
PostflightConfigBundle sets clearSyncStateBeforeApply when sync type is Clean/CleanAll, normalizes syncType to Normal, and mirrors pushTokenChain logic; SNTSyncManager docs updated.
Sync Service Tests
Source/santasyncservice/SNTSyncConfigBundleTest.mm, Source/santasyncservice/SNTSyncTest.mm
Adds tests verifying PostflightConfigBundle invariants (fullSyncLastSuccess, syncType normalization), pushTokenChain behavior, and that a clean postflight emits exactly one bundle with clearSyncStateBeforeApply == YES.
CLI Help
Source/santactl/Commands/SNTCommandSync.mm
Help text for --clean and --clean-all updated to state they also clear sync-managed settings.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Daemon
  participant Configurator
  participant Disk
  participant CELCache
  Client->>Daemon: request sync (preflight/postflight)
  Daemon->>Configurator: atomicallyApplyBundle(bundle) if bundle.clearSyncStateBeforeApply==YES
  Configurator->>Configurator: in-memory swap of syncState
  Configurator->>Disk: single write of sync-state.plist
  Configurator->>Daemon: apply complete
  Daemon->>CELCache: flush caches if CEL fallback rules changed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • northpolesec/santa#838: Both PRs modify the same configuration pipeline (SNTConfigBundle and SNTConfigurator and related sync/apply paths).
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: clearing sync state during clean syncs, which aligns with the primary objective evident across the changeset.
Description check ✅ Passed The description references ticket SNT-357 which is directly related to implementing clean sync state clearing functionality shown throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sbs/snt-357-clear-sync-state-on-clean-sync

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Source/santasyncservice/SNTSyncManager.mm (1)

332-353: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Release syncLimiter on the daemon-timeout early return.

At Line 347, the timeout path returns before the limiter is released at Line 353. This can leak a permit and eventually block future syncs with SNTSyncStatusTypeTooManySyncsInProgress.

🔧 Proposed fix
       if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
         SLOGE(@"Timeout waiting for daemon");
         if (reply) reply(SNTSyncStatusTypeDaemonTimeout);
+        dispatch_semaphore_signal(self.syncLimiter);
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/santasyncservice/SNTSyncManager.mm` around lines 332 - 353, The
daemon-timeout path in the dispatch_async block releases early without signaling
self.syncLimiter, leaking a permit; before returning on the timeout (the branch
after dispatch_semaphore_wait(sema,...)), call
dispatch_semaphore_signal(self.syncLimiter) (and then invoke
reply(SNTSyncStatusTypeDaemonTimeout)) so the semaphore is always released when
aborting, keeping syncLimiter consistent for future calls (affecting the block
that creates sema and calls [[self.daemonConn remoteObjectProxy]
updateSyncSettings:...] and the surrounding reply/preflightWithKeepOldSettings
logic).
🧹 Nitpick comments (1)
Source/santasyncservice/SNTSyncTest.mm (1)

1168-1218: ⚡ Quick win

Strengthen routing tests with negative expectations.

At Line 1178, Line 1191, Line 1204, and Line 1217 you only verify the expected selector was called. These tests can still pass if both replaceSyncSettings: and updateSyncSettings: are invoked in the same run. Add OCMReject for the non-expected path in each test.

♻️ Suggested test hardening
 - (void)testPostflightCleanSyncRoutesToReplaceSyncSettings {
   [self setupDefaultDaemonConnResponses];
   self.syncState.syncType = SNTSyncTypeClean;
   self.syncState.keepOldSettings = NO;

   SNTSyncPostflight* sut = [[SNTSyncPostflight alloc] initWithState:self.syncState];

   [self stubRequestBody:nil response:nil error:nil validateBlock:nil];

+  OCMReject([self.daemonConnRop updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
   XCTAssertTrue([sut sync]);
   OCMVerify([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
 }

 - (void)testPostflightCleanAllSyncRoutesToReplaceSyncSettings {
   [self setupDefaultDaemonConnResponses];
   self.syncState.syncType = SNTSyncTypeCleanAll;
   self.syncState.keepOldSettings = NO;

   SNTSyncPostflight* sut = [[SNTSyncPostflight alloc] initWithState:self.syncState];

   [self stubRequestBody:nil response:nil error:nil validateBlock:nil];

+  OCMReject([self.daemonConnRop updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
   XCTAssertTrue([sut sync]);
   OCMVerify([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
 }

 - (void)testPostflightCleanSyncWithKeepOldSettingsRoutesToUpdateSyncSettings {
   [self setupDefaultDaemonConnResponses];
   self.syncState.syncType = SNTSyncTypeClean;
   self.syncState.keepOldSettings = YES;

   SNTSyncPostflight* sut = [[SNTSyncPostflight alloc] initWithState:self.syncState];

   [self stubRequestBody:nil response:nil error:nil validateBlock:nil];

+  OCMReject([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
   XCTAssertTrue([sut sync]);
   OCMVerify([self.daemonConnRop updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
 }

 - (void)testPostflightCleanAllSyncWithKeepOldSettingsRoutesToUpdateSyncSettings {
   [self setupDefaultDaemonConnResponses];
   self.syncState.syncType = SNTSyncTypeCleanAll;
   self.syncState.keepOldSettings = YES;

   SNTSyncPostflight* sut = [[SNTSyncPostflight alloc] initWithState:self.syncState];

   [self stubRequestBody:nil response:nil error:nil validateBlock:nil];

+  OCMReject([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
   XCTAssertTrue([sut sync]);
   OCMVerify([self.daemonConnRop updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/santasyncservice/SNTSyncTest.mm` around lines 1168 - 1218, For each
test (testPostflightCleanSyncRoutesToReplaceSyncSettings,
testPostflightCleanAllSyncRoutesToReplaceSyncSettings,
testPostflightCleanSyncWithKeepOldSettingsRoutesToUpdateSyncSettings,
testPostflightCleanAllSyncWithKeepOldSettingsRoutesToUpdateSyncSettings) add a
negative expectation on self.daemonConnRop for the non-expected selector (use
OCMReject([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY])
in the tests that expect updateSyncSettings, and OCMReject([self.daemonConnRop
updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]) in the tests that expect
replaceSyncSettings) before calling [sut sync] so the test fails if both paths
are invoked. Ensure the OCMReject calls are placed after
setupDefaultDaemonConnResponses/stubs and before the XCTAssertTrue([sut sync])
invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/common/SNTConfigurator.mm`:
- Around line 2156-2160: The atomic-replace path is writing kFullSyncLastSuccess
without preserving the coupled kRuleSyncLastSuccess and can clear the rule-sync
timestamp; when handling PostflightConfigBundle/fullSyncLastSuccess (see the
bundle fullSyncLastSuccess:block and the
updateSyncStateForKey:kFullSyncLastSuccess call) ensure you also update
kRuleSyncLastSuccess to the same NSDate (use the existing
updateSyncStateForKey:kRuleSyncLastSuccess value:val writeToDisk:NO pattern) so
the full-sync → rule-sync coupling is preserved (or, alternately, change the
replace path to merge existing rule timestamp rather than overwrite it).

---

Outside diff comments:
In `@Source/santasyncservice/SNTSyncManager.mm`:
- Around line 332-353: The daemon-timeout path in the dispatch_async block
releases early without signaling self.syncLimiter, leaking a permit; before
returning on the timeout (the branch after dispatch_semaphore_wait(sema,...)),
call dispatch_semaphore_signal(self.syncLimiter) (and then invoke
reply(SNTSyncStatusTypeDaemonTimeout)) so the semaphore is always released when
aborting, keeping syncLimiter consistent for future calls (affecting the block
that creates sema and calls [[self.daemonConn remoteObjectProxy]
updateSyncSettings:...] and the surrounding reply/preflightWithKeepOldSettings
logic).

---

Nitpick comments:
In `@Source/santasyncservice/SNTSyncTest.mm`:
- Around line 1168-1218: For each test
(testPostflightCleanSyncRoutesToReplaceSyncSettings,
testPostflightCleanAllSyncRoutesToReplaceSyncSettings,
testPostflightCleanSyncWithKeepOldSettingsRoutesToUpdateSyncSettings,
testPostflightCleanAllSyncWithKeepOldSettingsRoutesToUpdateSyncSettings) add a
negative expectation on self.daemonConnRop for the non-expected selector (use
OCMReject([self.daemonConnRop replaceSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY])
in the tests that expect updateSyncSettings, and OCMReject([self.daemonConnRop
updateSyncSettings:OCMOCK_ANY reply:OCMOCK_ANY]) in the tests that expect
replaceSyncSettings) before calling [sut sync] so the test fails if both paths
are invoked. Ensure the OCMReject calls are placed after
setupDefaultDaemonConnResponses/stubs and before the XCTAssertTrue([sut sync])
invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e155e47-bbef-4782-ac51-f1286e11cf81

📥 Commits

Reviewing files that changed from the base of the PR and between 801d6c9 and ed4bac4.

📒 Files selected for processing (17)
  • Source/common/BUILD
  • Source/common/SNTConfigurator.h
  • Source/common/SNTConfigurator.mm
  • Source/common/SNTConfiguratorTest.mm
  • Source/common/SNTXPCControlInterface.h
  • Source/common/SNTXPCSyncServiceInterface.h
  • Source/gui/SNTAboutWindowView.swift
  • Source/gui/SNTStatusItemManager.mm
  • Source/santactl/Commands/SNTCommandSync.mm
  • Source/santad/SNTDaemonControlController.mm
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm
  • Source/santasyncservice/SNTSyncManager.h
  • Source/santasyncservice/SNTSyncManager.mm
  • Source/santasyncservice/SNTSyncPostflight.mm
  • Source/santasyncservice/SNTSyncService.mm
  • Source/santasyncservice/SNTSyncState.h
  • Source/santasyncservice/SNTSyncTest.mm

Comment thread Source/common/SNTConfigurator.mm Outdated
Comment thread Source/common/SNTConfigurator.mm Outdated
Comment thread Source/santactl/Commands/SNTCommandSync.mm Outdated
Comment thread Source/santad/SNTDaemonControlController.mm Outdated
@github-actions github-actions Bot added the size/m Size: medium label May 5, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/santad/SNTDaemonControlController.mm`:
- Around line 476-487: The current syncType handler clears persisted sync state
whenever SNTSyncTypeNormal is observed, but SNTSyncTypeNormal can be emitted for
ordinary sync starts; modify the logic around the [result syncType:^(SNTSyncType
val) { ... }] block so that clearSyncState is only called when this Normal
update is coming from a postflight bundle. Concretely, gate the call to
[configurator clearSyncState] by also checking the postflight signal already
present on the bundle (use the bundle/result's fullSyncLastSuccess value): only
call clearSyncState when val == SNTSyncTypeNormal AND the bundle's
fullSyncLastSuccess flag is true (the postflight-only indicator used by
PostflightConfigBundle), leaving other SNTSyncTypeNormal events untouched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ff2e047-c6e0-4e57-a229-da44a5594ab2

📥 Commits

Reviewing files that changed from the base of the PR and between ed4bac4 and be8f63b.

📒 Files selected for processing (9)
  • Source/common/SNTConfigurator.h
  • Source/common/SNTConfigurator.mm
  • Source/common/SNTConfiguratorTest.mm
  • Source/common/SNTXPCControlInterface.h
  • Source/santactl/Commands/SNTCommandSync.mm
  • Source/santad/SNTDaemonControlController.mm
  • Source/santasyncservice/SNTSyncConfigBundle.mm
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm
  • Source/santasyncservice/SNTSyncManager.h
✅ Files skipped from review due to trivial changes (1)
  • Source/santactl/Commands/SNTCommandSync.mm

Comment thread Source/santad/SNTDaemonControlController.mm Outdated
mlw
mlw previously requested changes May 6, 2026
Comment thread Source/santasyncservice/SNTSyncConfigBundleTest.mm Outdated
Comment thread Source/santad/SNTDaemonControlController.mm Outdated
Comment thread Source/santad/SNTDaemonControlController.mm Outdated
@sharvilshah
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Source/santasyncservice/SNTSyncConfigBundle.mm (1)

72-76: ⚡ Quick win

Update this comment to describe the new signal.

The daemon no longer takes the atomic-reset path because syncType == @(Normal); it now keys off clearSyncStateBeforeApply. Leaving the old explanation here makes this flow easy to misread during the next change.

✏️ Suggested comment update
-  // Clean syncs reset SyncTypeRequired to Normal. The daemon recognizes
-  // this exact transition (`syncType == @(Normal)`) as the signal that
-  // this is a clean-sync postflight bundle and clears persisted sync
-  // state before applying.
+  // Clean syncs reset SyncTypeRequired to Normal in postflight.
+  // The daemon's atomic-replace path is driven by
+  // clearSyncStateBeforeApply; syncType here only resets the persisted
+  // SyncTypeRequired value back to Normal.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/santasyncservice/SNTSyncConfigBundle.mm` around lines 72 - 76, Update
the explanatory comment above the bundle.syncType assignment (the block that
mentions SyncTypeRequired/Normal) to reflect the new behavior: the daemon no
longer uses the atomic-reset trigger of syncType == @(SNTSyncTypeNormal); it now
checks the clearSyncStateBeforeApply flag to decide to clear persisted sync
state before applying a clean-sync bundle. Mention that bundle.syncType is still
set to Normal for backwards compatibility but the actual reset signal is
clearSyncStateBeforeApply, and remove or correct the legacy wording that implies
the daemon keys off syncType alone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/santad/SNTDaemonControlController.mm`:
- Around line 495-498: The nil-safe comparison for CEL fallback data is wrong:
when both oldCELData and newCELData are nil the current isEqualToData: call
returns NO and triggers an unnecessary cache flush. Update the conditional
around newCELData/oldCELData (produced by [SNTCELFallbackRule serializeArray:])
to treat nil==nil as equal — e.g., only flush via
self.flushCacheBlock(FlushCacheMode::kAllCaches,
FlushCacheReason::kCELFallbackRulesChanged) when the two pointers are not both
nil and are not equal (use an explicit nil check combined with isEqualToData:),
ensuring the atomic path won’t flush caches on clean syncs.

---

Nitpick comments:
In `@Source/santasyncservice/SNTSyncConfigBundle.mm`:
- Around line 72-76: Update the explanatory comment above the bundle.syncType
assignment (the block that mentions SyncTypeRequired/Normal) to reflect the new
behavior: the daemon no longer uses the atomic-reset trigger of syncType ==
@(SNTSyncTypeNormal); it now checks the clearSyncStateBeforeApply flag to decide
to clear persisted sync state before applying a clean-sync bundle. Mention that
bundle.syncType is still set to Normal for backwards compatibility but the
actual reset signal is clearSyncStateBeforeApply, and remove or correct the
legacy wording that implies the daemon keys off syncType alone.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2dca3faa-46a5-4bf1-9df5-f0be7c1b4f48

📥 Commits

Reviewing files that changed from the base of the PR and between be8f63b and 364340f.

📒 Files selected for processing (12)
  • Source/common/SNTConfigBundle.h
  • Source/common/SNTConfigBundle.mm
  • Source/common/SNTConfigBundleTest.mm
  • Source/common/SNTConfigurator.h
  • Source/common/SNTConfigurator.mm
  • Source/common/SNTConfiguratorTest.mm
  • Source/common/SNTXPCControlInterface.h
  • Source/santad/SNTDaemonControlController.mm
  • Source/santasyncservice/SNTSyncConfigBundle.mm
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm
  • Source/santasyncservice/SNTSyncManager.h
  • Source/santasyncservice/SNTSyncTest.mm
✅ Files skipped from review due to trivial changes (1)
  • Source/santasyncservice/SNTSyncManager.h
🚧 Files skipped from review as they are similar to previous changes (3)
  • Source/common/SNTXPCControlInterface.h
  • Source/common/SNTConfigurator.mm
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm

Comment thread Source/santad/SNTDaemonControlController.mm
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/santasyncservice/SNTSyncConfigBundleTest.mm`:
- Around line 300-311: Add a symmetric test case to
testPostflightConfigBundleOmitsPushTokenChainWhenJWTsMissing: after the existing
checks, set syncState.pushJWT to a non-nil value while leaving
syncState.pushIssuerJWT nil and call PostflightConfigBundle(syncState) again,
then assert that bundle.pushTokenChain is nil; this mirrors the existing check
for pushIssuerJWT-only and ensures SNTSyncState + PostflightConfigBundle
correctly avoids returning a half-chain when only pushJWT or only pushIssuerJWT
is present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4952020-8fb7-47f0-bed0-477a4dcbb6dd

📥 Commits

Reviewing files that changed from the base of the PR and between 364340f and ecb47cf.

📒 Files selected for processing (1)
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm

Comment thread Source/santasyncservice/SNTSyncConfigBundleTest.mm
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
Source/common/SNTConfigurator.mm (1)

2144-2148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the full-sync → rule-sync timestamp coupling in the atomic path.

-setFullSyncLastSuccess: still updates both timestamps, but this replace path only writes kFullSyncLastSuccess. On clean/full postflight, the bundle sets fullSyncLastSuccess without ruleSyncLastSuccess, so the atomic swap can erase the rule-sync timestamp and make the daemon treat rules as never synced.

Suggested fix
   [bundle fullSyncLastSuccess:^(NSDate* v) {
     newSyncState[kFullSyncLastSuccess] = v;
+    newSyncState[kRuleSyncLastSuccess] = v;
   }];
   [bundle ruleSyncLastSuccess:^(NSDate* v) {
     newSyncState[kRuleSyncLastSuccess] = v;
   }];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/common/SNTConfigurator.mm` around lines 2144 - 2148, The atomic
replace path currently only writes kFullSyncLastSuccess and can drop the
rule-sync timestamp; update the atomic swap to preserve the full-sync →
rule-sync coupling by writing both kFullSyncLastSuccess and kRuleSyncLastSuccess
when the bundle provides both values (or when setFullSyncLastSuccess is used) so
newSyncState includes kRuleSyncLastSuccess alongside kFullSyncLastSuccess;
locate the blocks/bindings that set newSyncState (the bundle fullSyncLastSuccess
and ruleSyncLastSuccess handlers and the atomic swap that commits newSyncState)
and ensure both keys are assigned together before the atomic commit.
Source/santad/SNTDaemonControlController.mm (1)

495-497: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat nil CEL snapshots as equal in this comparison.

If both serializeArray(...) calls return nil, [oldCELData isEqualToData:newCELData] evaluates false via nil messaging, so every clean sync with no CEL fallback rules still flushes caches.

Suggested fix
-    if (![oldCELData isEqualToData:newCELData] && self.flushCacheBlock) {
+    if (oldCELData != newCELData && ![oldCELData isEqualToData:newCELData] &&
+        self.flushCacheBlock) {
       self.flushCacheBlock(FlushCacheMode::kAllCaches, FlushCacheReason::kCELFallbackRulesChanged);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/santad/SNTDaemonControlController.mm` around lines 495 - 497, The
comparison currently treats two nil snapshots as different because `[oldCELData
isEqualToData:newCELData]` is called when both are nil; update the check around
oldCELData/newCELData in SNTDaemonControlController.mm so that if both
`oldCELData` and `newCELData` are nil the branch is considered equal and does
not call `self.flushCacheBlock`, but if either is non-nil compare them with
`isEqualToData:` and only call `self.flushCacheBlock(FlushCacheMode::kAllCaches,
FlushCacheReason::kCELFallbackRulesChanged)` when they differ; reference
`oldCELData`, `newCELData`, `serializeArray:`, and `flushCacheBlock` to locate
and modify the conditional.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/santasyncservice/SNTSyncConfigBundle.mm`:
- Around line 72-76: The comment is outdated: the daemon no longer uses
bundle.syncType == @(SNTSyncTypeNormal) to signal a clean-sync; it now uses
clearSyncStateBeforeApply. Update the comment near the bundle.syncType
assignment (and references to syncState/SNTSyncTypeNormal) to state that we
explicitly set clearSyncStateBeforeApply to trigger wiping persisted sync state
before applying, removing any mention that the daemon keys off syncType ==
@(SNTSyncTypeNormal).

---

Duplicate comments:
In `@Source/common/SNTConfigurator.mm`:
- Around line 2144-2148: The atomic replace path currently only writes
kFullSyncLastSuccess and can drop the rule-sync timestamp; update the atomic
swap to preserve the full-sync → rule-sync coupling by writing both
kFullSyncLastSuccess and kRuleSyncLastSuccess when the bundle provides both
values (or when setFullSyncLastSuccess is used) so newSyncState includes
kRuleSyncLastSuccess alongside kFullSyncLastSuccess; locate the blocks/bindings
that set newSyncState (the bundle fullSyncLastSuccess and ruleSyncLastSuccess
handlers and the atomic swap that commits newSyncState) and ensure both keys are
assigned together before the atomic commit.

In `@Source/santad/SNTDaemonControlController.mm`:
- Around line 495-497: The comparison currently treats two nil snapshots as
different because `[oldCELData isEqualToData:newCELData]` is called when both
are nil; update the check around oldCELData/newCELData in
SNTDaemonControlController.mm so that if both `oldCELData` and `newCELData` are
nil the branch is considered equal and does not call `self.flushCacheBlock`, but
if either is non-nil compare them with `isEqualToData:` and only call
`self.flushCacheBlock(FlushCacheMode::kAllCaches,
FlushCacheReason::kCELFallbackRulesChanged)` when they differ; reference
`oldCELData`, `newCELData`, `serializeArray:`, and `flushCacheBlock` to locate
and modify the conditional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e536573c-3354-497a-96eb-7a0527c5c97b

📥 Commits

Reviewing files that changed from the base of the PR and between 801d6c9 and ecb47cf.

📒 Files selected for processing (14)
  • Source/common/BUILD
  • Source/common/SNTConfigBundle.h
  • Source/common/SNTConfigBundle.mm
  • Source/common/SNTConfigBundleTest.mm
  • Source/common/SNTConfigurator.h
  • Source/common/SNTConfigurator.mm
  • Source/common/SNTConfiguratorTest.mm
  • Source/common/SNTXPCControlInterface.h
  • Source/santactl/Commands/SNTCommandSync.mm
  • Source/santad/SNTDaemonControlController.mm
  • Source/santasyncservice/SNTSyncConfigBundle.mm
  • Source/santasyncservice/SNTSyncConfigBundleTest.mm
  • Source/santasyncservice/SNTSyncManager.h
  • Source/santasyncservice/SNTSyncTest.mm

Comment thread Source/santasyncservice/SNTSyncConfigBundle.mm Outdated
@sharvilshah sharvilshah added this to the 2026.4 milestone May 7, 2026
@sharvilshah sharvilshah marked this pull request as ready for review May 7, 2026 14:43
}
}

- (void)atomicallyApplyBundle:(SNTConfigBundle*)bundle {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't duplicate all the logic from the individual setSyncServer* methods.

// TODO: Start a timer to flush the state to disk. On startup, Santa should
// check for the presence of the state file and, if no SyncBaseURL is
// configured, start the timer to clear sync state and flush to disk.
void (^block)(void) = ^{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we should add an "is authorized" check:

  if (!self.syncStateAccessAuthorizerBlock()) {
    return;
  }

newSyncState[kExportConfigurationKey] = [v serialize];
}];
[bundle fullSyncLastSuccess:^(NSDate* v) {
newSyncState[kFullSyncLastSuccess] = v;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discrepancy - the rule sync last success is also set on the setter side, but not here.


// Side-effects pass. Persistence is already done by atomicallyApplyBundle:
// for every slot except modeTransition.
[result modeTransition:^(SNTModeTransition* val) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will result in not being part of the atomic swap. I don't think there are any KVO watchers for it today, but if any were added in the future it would result in unexpected fires.

@sharvilshah sharvilshah marked this pull request as draft May 7, 2026 15:11
@sharvilshah
Copy link
Copy Markdown
Contributor Author

Closing this in favor of #944

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/common comp/gui Issues or PRs related to the Santa GUI comp/santactl Issues or PRs related to santactl comp/santad Issues or PRs related to the daemon comp/santasyncservice Issues or PRs related to the sync protocol configurator Issues or PRs related to the configurator / Santa configuration lang/objc++ PRs modifying files in ObjC++ lang/swift PRs modifying files in Swift size/l Size: large size/m Size: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants