Skip to content

Refactored logic for Device becomes Online - #1683

Open
andrii-flamingo wants to merge 15 commits into
mainfrom
feature/device-becomes-online-dispatch
Open

Refactored logic for Device becomes Online#1683
andrii-flamingo wants to merge 15 commits into
mainfrom
feature/device-becomes-online-dispatch

Conversation

@andrii-flamingo

@andrii-flamingo andrii-flamingo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added automatic tracking of each machine’s first online event.
    • Added background processing to dispatch eligible online-triggered schedules.
    • Added tenant-aware handling with configurable processing intervals and batch sizes.
  • Bug Fixes
    • Prevented duplicate first-online events from creating repeated dispatch records.
    • Machines that are offline, unavailable, or missing required schedules remain pending for later processing.
    • Processing errors are isolated so other pending events continue to be handled.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b0bba752-b2f6-40f5-8b04-901a026a58c6

📥 Commits

Reviewing files that changed from the base of the PR and between 976e6d2 and 0a880a7.

📒 Files selected for processing (2)
  • openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java
  • openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java
  • openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java

📝 Walkthrough

Walkthrough

The change records first-online events in MongoDB, processes pending records in tenant-scoped batches, dispatches matching online schedules, and marks successful records as dispatched through a scheduled, ShedLock-coordinated job.

Changes

Device-online dispatch workflow

Layer / File(s) Summary
Capture first-online records
openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java, openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java, openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineScheduleTriggerService.java, openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineScheduleTriggerServiceTest.java
The online trigger stores a unique tenant-machine record with first-seen and dispatch timestamps. Duplicate records are ignored.
Process pending dispatches
openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java, openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineRepository.java, openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/rmm/ScriptScheduleMachineAssignedRepository.java, openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/rmm/ScriptScheduleRepository.java, openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java
The service groups pending records by tenant, loads machines and assignments, filters active DEVICE_ONLINE schedules, dispatches for online machines, and bulk-marks successful records. Failed or unavailable records remain pending.
Schedule periodic processing
openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java
The scheduler invokes processPending() at a configurable fixed delay. ShedLock coordinates execution, and failures are logged.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Machine
  participant DeviceOnlineScheduleTriggerService
  participant MachineFirstOnlineDispatchRepository
  participant DeviceOnlineDispatchScheduler
  participant DeviceOnlineDispatchService
  participant ScriptScheduleRepository
  Machine->>DeviceOnlineScheduleTriggerService: report online event
  DeviceOnlineScheduleTriggerService->>MachineFirstOnlineDispatchRepository: save first-online record
  DeviceOnlineDispatchScheduler->>DeviceOnlineDispatchService: processPending()
  DeviceOnlineDispatchService->>MachineFirstOnlineDispatchRepository: read undispatched records
  DeviceOnlineDispatchService->>ScriptScheduleRepository: load active DEVICE_ONLINE schedules
  DeviceOnlineDispatchService->>MachineFirstOnlineDispatchRepository: mark successful records dispatched
Loading

Possibly related PRs

Suggested reviewers: aliaska-varieva, oleksandrd-flamingo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: refactoring the logic for when a device becomes online.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/device-becomes-online-dispatch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java (1)

18-18: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Bound the pending query in the database.

findByDispatchedAtIsNull() returns every pending record. DeviceOnlineDispatchService.processPending then discards all rows beyond batchSize on line 53. During a backlog, each tick loads the full pending set into memory and transfers it over the wire. Push the cap into the query instead.

♻️ Proposed bounded query
-    List<MachineFirstOnlineDispatch> findByDispatchedAtIsNull();
+    List<MachineFirstOnlineDispatch> findByDispatchedAtIsNull(Pageable pageable);

Then call it with PageRequest.of(0, batchSize, Sort.by("firstSeenAt")) in DeviceOnlineDispatchService.processPending. Sorting by firstSeenAt also makes the drain order deterministic and prevents starvation of older rows.

🤖 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
`@openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java`
at line 18, Bound the pending-dispatch query instead of loading all records:
update MachineFirstOnlineDispatchRepository.findByDispatchedAtIsNull to accept
paging, then update DeviceOnlineDispatchService.processPending to call it with
PageRequest.of(0, batchSize, Sort.by("firstSeenAt")). Preserve processing of
only the requested page and remove any redundant in-memory truncation.
openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java (1)

164-194: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a record with a null tenantId.

No test exercises a pending record with a null tenantId. That input currently throws NullPointerException inside Collectors.groupingBy in DeviceOnlineDispatchService.processPending and aborts the whole tick. Add the case after you apply the guard.

💚 Proposed test
`@Test`
`@DisplayName`("row with null tenantId → skipped, healthy rows still flush")
void nullTenantRow_doesNotKillTheTick() {
    MachineFirstOnlineDispatch orphan = row("row-orphan", "m-orphan", null);
    MachineFirstOnlineDispatch good = row("row-good", "m-good", TENANT);
    when(dispatchRepository.findByDispatchedAtIsNull()).thenReturn(List.of(orphan, good));
    stubTenantReads(TENANT, List.of(onlineMachine("m-good", TENANT)), List.of(), List.of());

    service.processPending();

    ArgumentCaptor<Collection<String>> ids = ArgumentCaptor.forClass(Collection.class);
    verify(dispatchRepository).markDispatchedIn(ids.capture(), any(Instant.class));
    assertThat(ids.getValue()).containsExactly("row-good");
}
🤖 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
`@openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java`
around lines 164 - 194, Update DeviceOnlineDispatchService.processPending to
skip pending records with a null tenantId before grouping by tenant, while
continuing to process and mark valid tenant rows in the same tick. Add a test in
DeviceOnlineDispatchServiceTest covering one null-tenant row and one healthy
row, asserting only the healthy row is flushed.
openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineRepository.java (1)

28-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the single-machine lookup from MachineRepository.

findByTenantIdAndMachineId(String tenantId, String machineId) is only declared in MachineRepository; the dispatch/services flow uses findByTenantIdAndMachineIdIn. Drop the unused method or wire it into the caller before keeping it.

🤖 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
`@openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineRepository.java`
at line 28, Remove the unused findByTenantIdAndMachineId method declaration from
MachineRepository, since the dispatch/services flow uses
findByTenantIdAndMachineIdIn instead. Do not alter the existing collection
lookup.
🤖 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
`@openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java`:
- Around line 17-21: Update the scheduling flow around processPending and the
deviceOnlineDispatch SchedulerLock so concurrent replicas cannot process the
same pending rows: either configure lockAtMostFor above the worst-case batch
duration, or atomically claim each row before tenant reads and NATS dispatches,
handling claim conflicts with retries. Preserve the existing dispatch behavior
after rows are safely protected.

In
`@openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java`:
- Around line 111-119: Update the pending dispatch flow around machinesById
lookup and status validation so deleted or persistently OFFLINE machines receive
a terminal outcome and are removed from pending rather than retried
indefinitely. Use the existing dispatched marker or an appropriate
skipped/age-expiration field, with the configured cutoff if needed. Also make
the pending-record query order by firstSeenAt before applying the batch cap so
selection is deterministic.
- Around line 56-57: Update the batching flow in DeviceOnlineDispatchService to
filter out records with a null tenantId before the
groupingBy(MachineFirstOnlineDispatch::getTenantId) call, and log each discarded
record. Ensure these invalid records are drained without allowing grouping to
throw, while preserving grouping and per-tenant processing for valid records.

In
`@openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java`:
- Around line 19-25: Ensure the unique tenant_machineId index declared on
MachineFirstOnlineDispatch is actually created at runtime or through a Mongo
migration. Add explicit startup index creation or a migration for the
machine_first_online_dispatch collection, preserving uniqueness across each
(tenantId, machineId) pair so DeviceOnlineScheduleTriggerService can rely on
DuplicateKeyException.

In
`@openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java`:
- Around line 20-22: Update the caller and success-tracking logic around
MachineFirstOnlineDispatchRepository.markDispatchedIn to treat its return value
as the modified-document count, not the matched ID count. Do not classify
already-dispatched IDs with the same dispatchedAt value as an update degradation
or omit them from successful tracking; preserve normal handling for genuinely
unmatched IDs.

---

Nitpick comments:
In
`@openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java`:
- Around line 164-194: Update DeviceOnlineDispatchService.processPending to skip
pending records with a null tenantId before grouping by tenant, while continuing
to process and mark valid tenant rows in the same tick. Add a test in
DeviceOnlineDispatchServiceTest covering one null-tenant row and one healthy
row, asserting only the healthy row is flushed.

In
`@openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java`:
- Line 18: Bound the pending-dispatch query instead of loading all records:
update MachineFirstOnlineDispatchRepository.findByDispatchedAtIsNull to accept
paging, then update DeviceOnlineDispatchService.processPending to call it with
PageRequest.of(0, batchSize, Sort.by("firstSeenAt")). Preserve processing of
only the requested page and remove any redundant in-memory truncation.

In
`@openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineRepository.java`:
- Line 28: Remove the unused findByTenantIdAndMachineId method declaration from
MachineRepository, since the dispatch/services flow uses
findByTenantIdAndMachineIdIn instead. Do not alter the existing collection
lookup.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8905ecfa-6948-4111-bfbb-c453c272e687

📥 Commits

Reviewing files that changed from the base of the PR and between e0bc6ee and 6a03dc6.

📒 Files selected for processing (10)
  • openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java
  • openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java
  • openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineScheduleTriggerService.java
  • openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineDispatchServiceTest.java
  • openframe-client-core/src/test/java/com/openframe/client/service/DeviceOnlineScheduleTriggerServiceTest.java
  • openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java
  • openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java
  • openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineRepository.java
  • openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/rmm/ScriptScheduleMachineAssignedRepository.java
  • openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/rmm/ScriptScheduleRepository.java

Comment on lines +17 to +21
@Scheduled(fixedDelayString = "${openframe.rmm.device-online.dispatch.interval:60000}")
@SchedulerLock(name = "deviceOnlineDispatch",
lockAtMostFor = "${openframe.rmm.device-online.dispatch.lock-at-most-for:2m}",
lockAtLeastFor = "${openframe.rmm.device-online.dispatch.lock-at-least-for:10s}"
)

@coderabbitai coderabbitai Bot Aug 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm ShedLock is enabled and a LockProvider bean exists.
rg -nP --type=java -C3 'EnableSchedulerLock|LockProvider|EnableScheduling'
rg -n 'shedlock' --glob 'pom.xml' --glob 'build.gradle*' -i

Repository: flamingo-stack/openframe-oss-lib

Length of output: 170


🏁 Script executed:

#!/bin/bash
set -u

echo "## candidate files"
git ls-files | rg 'DeviceOnlineDispatchScheduler|DeviceOnlineDispatchService|SchedulerLock|LockProvider|EnableSchedulerLock|EnableScheduling' || true

echo "## tracked Java files with annotations/classes of interest"
rg -n '`@Scheduled`|`@SchedulerLock`|EnableScheduling|EnableSchedulerLock|LockProvider' --type=java . || true

echo "## dependency/search for shedlock"
rg -n 'shedlock|shed-lock|net.javacrumbs|schedlock' -i --glob 'pom.xml' --glob 'build.gradle*' --glob 'settings.gradle*' --glob 'gradle.properties' . || true

Repository: flamingo-stack/openframe-oss-lib

Length of output: 7701


🏁 Script executed:

#!/bin/bash
set -u

echo "## DeviceOnlineDispatchScheduler.java"
cat -n openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java

echo "## DeviceOnlineDispatchService.java outline"
ast-grep outline openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java || true

echo "## DeviceOnlineDispatchService.java relevant content"
wc -l openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java
cat -n openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java

echo "## ShedLock client config"
cat -n openframe-client-core/src/main/java/com/openframe/client/config/ShedLockConfig.java

echo "## pom shedlock excerpts"
sed -n '40,70p' openframe-client-core/pom.xml | cat -n

Repository: flamingo-stack/openframe-oss-lib

Length of output: 13044


Protect dispatch rows before cross-tenant work and size the lock above the batch duration.

processPending selects pending rows, reads per tenant, then marks rows dispatched only at the end. If a tenant’s reads plus NATS dispatches exceed lockAtMostFor (2 minutes), ShedLock releases the lock while later work still runs; a second replica can read the same pending rows and dispatch the same schedules twice. Extend lockAtMostFor above worst-case tick duration, or atomically mark each row as being processed before cross-tenant work and retry conflicts.

🤖 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
`@openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java`
around lines 17 - 21, Update the scheduling flow around processPending and the
deviceOnlineDispatch SchedulerLock so concurrent replicas cannot process the
same pending rows: either configure lockAtMostFor above the worst-case batch
duration, or atomically claim each row before tenant reads and NATS dispatches,
handling claim conflicts with retries. Preserve the existing dispatch behavior
after rows are safely protected.

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.

valid?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Scheduler"
cat -n openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java

echo "## Dispatch service structure"
ast-grep outline openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java --items all
echo "## Dispatch service"
cat -n openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java

echo "## Dispatch repository"
cat -n openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java

echo "## Relevant configuration and timeout/claim usages"
rg -n -i -C 3 \
  'device-online\.dispatch|processPending\s*\(|markDispatchedIn\s*\(|dispatchedAt|lock-at-most-for|timeout|Duration|claim|processing' \
  --glob '*.java' --glob '*.yml' --glob '*.yaml' --glob '*.properties' --glob 'pom.xml' .

Length of output: 201413


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java \
  openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java \
  openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java \
  openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java
do
  echo "## $file"
  cat -n "$file"
done

echo "## Exact direct usages and declarations"
rg -n -C 4 \
  'processPending\s*\(|markDispatchedIn\s*\(|findByDispatchedAtIsNull\s*\(|dispatch.*Schedule|schedule.*dispatch' \
  openframe-client-core/src/main/java/com/openframe/client \
  openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device \
  openframe-api-lib/src/main/java/com/openframe/api/service/rmm

Length of output: 25556


@kirill-567, yes. The finding is valid as a conditional concurrency risk.

processPending() reads up to 500 rows with dispatchedAt == null, dispatches schedules synchronously, and marks the rows only after all tenant work completes. The code has no processing claim and no bound that proves this work completes within the default two-minute lockAtMostFor.

If processing exceeds two minutes, another replica can acquire the ShedLock lock, read the same pending rows, and dispatch the same schedules.

This is not proof that normal executions exceed two minutes. It is a valid issue because the current code does not enforce that limit. The best fix is an atomic claim state with a lease and retry handling. Increasing lockAtMostFor is acceptable only if an operational maximum execution time is known and kept below that value.

You are interacting with an AI system.

Comment on lines +111 to +119
Machine machine = machinesById.get(row.getMachineId());
if (machine == null) {
log.warn("DEVICE_ONLINE dispatch: machine gone before first fire, tenantId={} machineId={} — leaving pending",
row.getTenantId(), row.getMachineId());
return Optional.empty();
}
if (machine.getStatus() != DeviceStatus.ONLINE) {
return Optional.empty();
}

@coderabbitai coderabbitai Bot Aug 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Rows for deleted machines never drain.

If a machine is deleted, machinesById.get(...) returns null on every tick, and the record stays pending forever. The pending set then grows without bound, and the warning on lines 113-114 repeats on each tick.

The same effect occurs for a machine that stays OFFLINE. Combined with the unordered in-memory cap on line 53, permanently stuck records can occupy the batch and delay newer records indefinitely.

Add a terminal outcome for these records. Two options:

  • Mark the record dispatched (or add a skippedAt/attempts field) when the machine no longer exists.
  • Add an age cutoff so records older than a configured limit are marked and removed from the pending set.

Also, order the pending query by firstSeenAt so the batch selection is deterministic.

🤖 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
`@openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java`
around lines 111 - 119, Update the pending dispatch flow around machinesById
lookup and status validation so deleted or persistently OFFLINE machines receive
a terminal outcome and are removed from pending rather than retried
indefinitely. Use the existing dispatched marker or an appropriate
skipped/age-expiration field, with the configured cutoff if needed. Also make
the pending-record query order by firstSeenAt before applying the batch cap so
selection is deterministic.

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.

valid?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

service='openframe-client-core/src/main/java/com/openframe/client/service/rmm/DeviceOnlineDispatchService.java'
repo='openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/device/MachineFirstOnlineDispatchRepository.java'
document='openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java'
scheduler='openframe-client-core/src/main/java/com/openframe/client/scheduler/DeviceOnlineDispatchScheduler.java'

ast-grep outline "$service" --items all
printf '\n--- DeviceOnlineDispatchService ---\n'
cat -n "$service"

printf '\n--- MachineFirstOnlineDispatchRepository ---\n'
cat -n "$repo"

printf '\n--- MachineFirstOnlineDispatch ---\n'
cat -n "$document"

printf '\n--- DeviceOnlineDispatchScheduler ---\n'
cat -n "$scheduler"

printf '\n--- Pending-record lifecycle references ---\n'
rg -n -C 3 --glob '*.java' \
  'MachineFirstOnlineDispatch|findByDispatchedAtIsNull|markDispatchedIn|dispatchedAt|firstSeenAt' \
  openframe-client-core openframe-data-mongo-common openframe-data-mongo-sync

Length of output: 132194


@kirill-567, partially valid.

The deleted-machine case is valid. processOne() leaves a missing machine pending, and no other code clears that record. The existing test also expects this behavior. Those records can remain pending forever.

The deterministic batching concern is also valid. findByDispatchedAtIsNull() has no sort, but processPending() applies limit(batchSize). MongoDB does not guarantee a useful order without an explicit sort. Stuck records can therefore delay other pending records.

The offline-machine part needs refinement. A machine can be offline only because the scheduler runs after the device disconnects. It must remain pending in that case. A terminal outcome should apply only after an explicit policy, such as a configured maximum age or retry limit.

I would keep the finding, but scope the required change to:

  1. Add a deterministic pending query, ordered by firstSeenAt and optionally _id as a tie-breaker.
  2. Add a terminal state for missing machines.
  3. If the product requires bounded retries for offline machines, add a configured age or retry cutoff with a separate skippedAt/reason field.

🐇🔎

You are interacting with an AI system.

Comment on lines +19 to +25
@CompoundIndex(
name = "tenant_machineId",
def = "{'tenantId': 1, 'machineId': 1}",
unique = true)
@CompoundIndex(
name = "dispatchedAt_pending",
def = "{'dispatchedAt': 1}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Mongo auto index creation config and index migrations.
rg -n --glob '*.{yml,yaml,properties}' 'auto-index-creation|autoIndexCreation'
rg -n 'MongoMappingContext|setAutoIndexCreation|IndexOperations|ensureIndex|createIndex' --glob '*.java'
fd -i 'mongock|migration|changelog|changeunit' -t d -t f | head -50

Repository: flamingo-stack/openframe-oss-lib

Length of output: 170


🏁 Script executed:

#!/bin/bash
set -u

echo "Repo files of interest:"
git ls-files | rg '(^|/)(Application|application|application-docker|application-local|application-dev|application-test|application-prod)\.(yml|yaml|properties)$|^(pom\.xml|build\.(gradle|kts))$|^openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch\.java$' || true

echo
echo "MachineFirstOnlineDispatch.java outline:"
ast-grep outline openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java 2>/dev/null || true
echo
echo "MachineFirstOnlineDispatch.java relevant lines:"
cat -n openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java | sed -n '1,100p'

echo
echo "Search mongo index creation patterns (all Java):"
rg -n --glob '*.java' 'setAutoIndexCreation|IndexOperations|ensureIndex|createIndex|MongoMappingContext|DuplicateKeyException|`@CompoundIndex`|`@CompoundIndexes`' . || true

echo
echo "Search auto index config (all tracked files):"
rg -n 'auto[-_]?index|autoIndexCreation|spring\.data\.mongodb|mongo' --glob '*.{yml,yaml,properties,json,xml,gradle,kts}' . || true

echo
echo "Dependency mentions for Spring Boot / Spring Data MongoDB / Mongo migrations:"
rg -n 'Spring Boot|spring-boot|spring-data-mongodb|MongoDB|Mongoock|mongoock|Mongock|mongo:|mongodb:' --glob '*.{xml,yml,yaml,properties,gradle,kts,md}' . || true

Repository: flamingo-stack/openframe-oss-lib

Length of output: 50389


Ensure machine_first_online_dispatch.tenant_machineId exists.

DeviceOnlineScheduleTriggerService uses DuplicateKeyException to prevent duplicate dispatch records for each (tenantId, machineId) pair. The collection currently has no ensureIndex/Mongo migration or auto-index setting, so add a migration or explicit startup index creation for this unique index.

🤖 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
`@openframe-data-mongo-common/src/main/java/com/openframe/data/document/device/MachineFirstOnlineDispatch.java`
around lines 19 - 25, Ensure the unique tenant_machineId index declared on
MachineFirstOnlineDispatch is actually created at runtime or through a Mongo
migration. Add explicit startup index creation or a migration for the
machine_first_online_dispatch collection, preserving uniqueness across each
(tenantId, machineId) pair so DeviceOnlineScheduleTriggerService can rely on
DuplicateKeyException.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants