Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ public class ScriptScheduleResponse {
private Instant statusChangedAt;
private Instant createdAt;
private Instant updatedAt;
private boolean testScript;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public class ScriptResponse {
private Instant statusChangedAt;
private Instant createdAt;
private Instant updatedAt;
private boolean testScript;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,17 @@
import com.openframe.data.document.rmm.script.Script;
import com.openframe.data.document.rmm.script.ScriptEnvVar;
import com.openframe.data.document.rmm.script.ScriptStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* Pure entity ↔ DTO mapping for scripts. Lives in {@code openframe-api-lib}
* so it can be reused by any service that talks to the script repository,
* regardless of transport (GraphQL / REST / messaging).
*
* <p>GraphQL-specific concerns (cursor pagination, Relay Connection / Edge
* envelope) live in {@code GraphQLScriptMapper} alongside the DGS resolver.
*/
@Component
public class ScriptMapper {

@Value("${openframe.rmm.test-mode.enabled}")
private boolean testModeEnabled;

Check failure on line 20 in openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptMapper.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

@Value property openframe.rmm.test-mode.enabled has no explicit default and no documented environment declaration

@value property openframe.rmm.test-mode.enabled has no explicit default and no documented environment declaration The new @value("${openframe.rmm.test-mode.enabled}") bindings (repeated in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService) have no inline default and there's no evidence in this diff that the property is declared in every environment's config. If the property is missing in any environment, Spring will fail to start that service (fail-fast is fine per the rule), but if it silently falls back due to inconsistent config layering it could cause test scripts/commands to leak into production data. Confirm the property is explicitly declared in all environment YAML files, or supply a safe default consistently.
Comment on lines +19 to +20

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.

🦩 🔴 [error/action_required] OFJAVA-011 @value property openframe.rmm.test-mode.enabled has no explicit default and no documented environment declaration

The new @value("${openframe.rmm.test-mode.enabled}") bindings (repeated in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService) have no inline default and there's no evidence in this diff that the property is declared in every environment's config. If the property is missing in any environment, Spring will fail to start that service (fail-fast is fine per the rule), but if it silently falls back due to inconsistent config layering it could cause test scripts/commands to leak into production data. Confirm the property is explicitly declared in all environment YAML files, or supply a safe default consistently.

Evidence
    @Value("${openframe.rmm.test-mode.enabled}")
    private boolean testModeEnabled;
🤖 Prompt for AI agents
In openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptMapper.java around lines 19-20, address this code-review finding: @Value property openframe.rmm.test-mode.enabled has no explicit default and no documented environment declaration.
The new @Value("${openframe.rmm.test-mode.enabled}") bindings (repeated in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService) have no inline default and there's no evidence in this diff that the property is declared in every environment's config. If the property is missing in any environment, Spring will fail to start that service (fail-fast is fine per the rule), but if it silently falls back due to inconsistent config layering it could cause test scripts/commands to leak into production data. Confirm the property is explicitly declared in all environment YAML files, or supply a safe default consistently.
The flagged code:
```
    @Value("${openframe.rmm.test-mode.enabled}")
    private boolean testModeEnabled;
```
Make the minimal change that resolves the finding; do not refactor unrelated code.

confidence: 40 — react 👍/👎 to teach the reviewer


public Script toEntity(String tenantId, CreateScriptInput input) {
return Script.builder()
.tenantId(tenantId)
Expand All @@ -35,6 +31,7 @@
.defaultTimeoutSeconds(input.getDefaultTimeoutSeconds())
.defaultArgs(input.getDefaultArgs())
.envVars(ScriptEnvVarMapper.toEntity(input.getEnvVars()))
.testScript(testModeEnabled)
.build();
}

Expand All @@ -50,25 +47,26 @@
existing.setEnvVars(ScriptEnvVarMapper.toEntity(input.getEnvVars()));
}

public ScriptResponse toResponse(Script entity) {
return ScriptResponse.builder()
.id(entity.getId())
.name(entity.getName())
.description(entity.getDescription())
.shell(entity.getShell())
.privilegeLevel(entity.getPrivilegeLevel() != null ? entity.getPrivilegeLevel() : PrivilegeLevel.USER)
.scriptBody(entity.getScriptBody())
.supportedPlatforms(entity.getSupportedPlatforms())
.defaultTimeoutSeconds(entity.getDefaultTimeoutSeconds())
.defaultArgs(entity.getDefaultArgs())
.envVars(mapEnvVarsToResponse(entity.getEnvVars()))
.createdBy(entity.getCreatedBy())
.status(entity.getStatus() != null ? entity.getStatus() : ScriptStatus.ACTIVE)
.statusChangedAt(entity.getStatusChangedAt())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.testScript(entity.isTestScript())
.build();
}

Check warning on line 69 in openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptMapper.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

toResponse duplicates a near-identical definition

toResponse duplicates a near-identical definition Nearly the same structure as [OpenFrame OSS Library:openframe-test-service-core/src/main/java/com/openframe/test/data/generator/ScriptGenerator.java:43](https://github.com/flamingo-stack/openframe-oss-lib/blob/78f0c703733110af11929f97560c812eb2ce3741/openframe-test-service-core/src/main/java/com/openframe/test/data/generator/ScriptGenerator.java#L43-L56) (java:com.openframe.test.data.generator.ScriptGenerator#updateScriptRequest, hamming 6). Reuse the existing definition, or extract one shared implementation.

private List<ScriptEnvVarInput> mapEnvVarsToResponse(List<ScriptEnvVar> envVars) {
if (envVars == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.openframe.data.document.rmm.schedule.ScheduleScriptTrigger;
import com.openframe.data.document.rmm.schedule.ScheduleTimeReference;
import com.openframe.data.document.rmm.script.ScriptStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;
Expand All @@ -23,6 +24,9 @@
@Component
public class ScriptScheduleMapper {

@Value("${openframe.rmm.test-mode.enabled}")
private boolean testModeEnabled;

public ScheduleScript toEntity(String tenantId, CreateScriptScheduleInput input) {
return ScheduleScript.builder()
.tenantId(tenantId)
Expand All @@ -37,6 +41,7 @@
.reconnectWindowSeconds(input.getReconnectWindowSeconds())
.startAt(input.getStartAt())
.repeat(input.getRepeat())
.testScript(testModeEnabled)
.build();
}

Expand Down Expand Up @@ -71,31 +76,32 @@
return mode != null ? mode : ScheduleDeviceSelectionMode.SPECIFIC;
}

public ScriptScheduleResponse toResponse(ScheduleScript entity) {
return ScriptScheduleResponse.builder()
.id(entity.getId())
.name(entity.getName())
.description(entity.getDescription())
.supportedPlatforms(entity.getSupportedPlatforms())
.scriptIds(entity.getScriptIds())
.scriptCustomParams(entity.getScriptCustomParams())
.selectionMode(defaultSelectionMode(entity.getSelectionMode()))
.deviceCriteria(entity.getDeviceCriteria())
.trigger(defaultTrigger(entity.getTrigger()))
.timeReference(defaultTimeReference(entity.getTimeReference()))
.offlineBehavior(defaultOfflineBehavior(entity.getOfflineBehavior()))
.reconnectWindowSeconds(entity.getReconnectWindowSeconds())
.startAt(entity.getStartAt())
.repeat(entity.getRepeat())
.nextRunAt(entity.getNextRunAt())
.lastRunAt(entity.getLastRunAt())
.createdBy(entity.getCreatedBy())
.status(entity.getStatus() != null ? entity.getStatus() : ScriptStatus.ACTIVE)
.statusChangedAt(entity.getStatusChangedAt())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.testScript(entity.isTestScript())
.build();
}

Check warning on line 104 in openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptScheduleMapper.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

toResponse duplicates a near-identical definition

toResponse duplicates a near-identical definition Nearly the same structure as [OpenFrame OSS Library:openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptExecutionMapper.java:11](https://github.com/flamingo-stack/openframe-oss-lib/blob/78f0c703733110af11929f97560c812eb2ce3741/openframe-api-lib/src/main/java/com/openframe/api/mapper/ScriptExecutionMapper.java#L11-L37) (java:com.openframe.api.mapper.ScriptExecutionMapper#toResponse, hamming 4). Reuse the existing definition, or extract one shared implementation.

private static List<ScheduledScriptCustomParams> toCustomParams(List<ScheduledScriptCustomParamsInput> input) {
if (input == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.openframe.data.nats.rmm.publisher.CommandNatsPublisher;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -37,6 +38,9 @@
private final DeviceService deviceService;
private final CommandExecutionService commandExecutionService;

@Value("${openframe.rmm.test-mode.enabled}")
private boolean testModeEnabled;

Check warning on line 42 in openframe-api-lib/src/main/java/com/openframe/api/service/rmm/command/CommandDispatchService.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

Inline property-key string repeated across four classes instead of using a shared constant/config class

Inline property-key string repeated across four classes instead of using a shared constant/config class The literal string "${openframe.rmm.test-mode.enabled}" is duplicated verbatim in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService. Per OFJAVA-005 (named constants for values with meaning) and OPENFRAM-002-21 (grouped configuration via @ConfigurationProperties), this flag should be centralized in a single @ConfigurationProperties-bound class (e.g. RmmTestModeProperties) injected everywhere it's needed, rather than repeating the same @value string in four unrelated components.
Comment on lines +41 to +42

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.

🦩 🟠 [warn/recommended] OFJAVA-005 Inline property-key string repeated across four classes instead of using a shared constant/config class

The literal string "${openframe.rmm.test-mode.enabled}" is duplicated verbatim in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService. Per OFJAVA-005 (named constants for values with meaning) and OPENFRAM-002-21 (grouped configuration via @ConfigurationProperties), this flag should be centralized in a single @ConfigurationProperties-bound class (e.g. RmmTestModeProperties) injected everywhere it's needed, rather than repeating the same @value string in four unrelated components.

Evidence
    @Value("${openframe.rmm.test-mode.enabled}")
    private boolean testModeEnabled;
🤖 Prompt for AI agents
In openframe-api-lib/src/main/java/com/openframe/api/service/rmm/command/CommandDispatchService.java around lines 41-42, address this code-review finding: Inline property-key string repeated across four classes instead of using a shared constant/config class.
The literal string "${openframe.rmm.test-mode.enabled}" is duplicated verbatim in ScriptMapper, ScriptScheduleMapper, CommandDispatchService, and ScriptDispatchService. Per OFJAVA-005 (named constants for values with meaning) and OPENFRAM-002-21 (grouped configuration via @ConfigurationProperties), this flag should be centralized in a single @ConfigurationProperties-bound class (e.g. RmmTestModeProperties) injected everywhere it's needed, rather than repeating the same @Value string in four unrelated components.
The flagged code:
```
    @Value("${openframe.rmm.test-mode.enabled}")
    private boolean testModeEnabled;
```
Make the minimal change that resolves the finding; do not refactor unrelated code.

confidence: 30 — react 👍/👎 to teach the reviewer


public DispatchResponse runCommand(RunCommandInput input) {
deviceService.verifyDispatchable(input.getMachineId());

Expand Down Expand Up @@ -76,7 +80,8 @@
// Persist one RUNNING row per machine (tenant-scoped, via the service) before
// anything hits the wire — the agent's result transitions each row later.
commandExecutionService.createBatch(executionId, input.getCommand(), input.getShell(),
machineIds, input.getPrivilegeLevel(), input.getTimeoutSeconds(), initiatedBy);
machineIds, input.getPrivilegeLevel(), input.getTimeoutSeconds(), initiatedBy,
testModeEnabled);

// Fan out the same payload (one executionId) to every machine.
CommandMessage message = CommandMessage.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,18 @@
* shared {@code executionId} — backs batch command dispatch. Unique constraint is
* {@code (tenantId, executionId, machineId)}.
*/
public List<CommandExecution> createBatch(String executionId,
String command,
ScriptShell shell,
List<String> machineIds,
PrivilegeLevel privilegeLevel,
Integer timeoutSeconds,
String initiatedBy) {
String initiatedBy,
boolean testCommand) {

Check warning on line 42 in openframe-api-lib/src/main/java/com/openframe/api/service/rmm/command/CommandExecutionService.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

CommandExecutionService.createBatch/buildRunningRow parameter list grows to 8 positional args, worsening an existing data clump

CommandExecutionService.createBatch/buildRunningRow parameter list grows to 8 positional args, worsening an existing data clump OFJAVA-022 requires long parameter lists / recurring data clumps (executionId, command, shell, machineIds, privilegeLevel, timeoutSeconds, initiatedBy, plus now testCommand) to be extracted into a parameter object. This PR adds an 8th positional boolean parameter to already-long method signatures (createBatch and buildRunningRow), compounding the data-clump problem instead of introducing a command/parameter object as required by the convention (note: per OFJAVA-033 this must be a Lombok class, not a record).
Instant now = Instant.now();
List<CommandExecution> rows = machineIds.stream()
.map(machineId -> buildRunningRow(executionId, command, shell, machineId,
privilegeLevel, timeoutSeconds, initiatedBy, now))
privilegeLevel, timeoutSeconds, initiatedBy, now, testCommand))
.toList();
List<CommandExecution> saved = commandExecutionRepository.saveAll(rows);
log.info("Persisted batch command execution rows: executionId={} machineCount={} initiatedBy={} status=RUNNING",
Expand Down Expand Up @@ -72,7 +73,8 @@
PrivilegeLevel privilegeLevel,
Integer timeoutSeconds,
String initiatedBy,
Instant now) {
Instant now,
boolean testCommand) {
return CommandExecution.builder()
.tenantId(tenantIdProvider.getTenantId())
.executionId(executionId)
Expand All @@ -85,6 +87,7 @@
.status(ExecutionStatus.RUNNING)
.dispatchedAt(now)
.statusChangedAt(now)
.testCommand(testCommand)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.openframe.data.service.TenantIdProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -64,6 +65,8 @@ public class ScriptDispatchService {
private final ScheduleScriptExecutionRepository scheduleScriptExecutionRepository;
private final TenantIdProvider tenantIdProvider;
private final ScriptTimeoutValidator timeoutValidator;
@Value("${openframe.rmm.test-mode.enabled}")
private boolean testModeEnabled;

public DispatchResponse runScript(RunScriptInput input, String initiatedBy, ExecutionSource source) {
timeoutValidator.validate(input.getTimeoutSeconds());
Expand All @@ -77,7 +80,8 @@ public DispatchResponse runScript(RunScriptInput input, String initiatedBy, Exec
// Persist the effective timeout on the row so the watchdog can derive a
// per-execution stuck-threshold from it.
scriptExecutionService.create(executionId, script.getId(),
input.getMachineId(), input.getPrivilegeLevel(), timeoutSeconds, initiatedBy, source);
input.getMachineId(), input.getPrivilegeLevel(), timeoutSeconds, initiatedBy, source,
testModeEnabled);

ScriptMessage message = ScriptMessage.builder()
.executionId(executionId)
Expand Down Expand Up @@ -185,6 +189,7 @@ public DispatchResponse runSchedule(String scheduleId, String initiatedBy) {
.status(ExecutionStatus.RUNNING)
.totalMachineCount(machineIds.size())
.dispatchedAt(now)
.testScript(testModeEnabled)
.build());

// 2. Leaves: N × M ScriptExecution rows (persist per-script batch), so the watchdog
Expand All @@ -196,7 +201,7 @@ public DispatchResponse runSchedule(String scheduleId, String initiatedBy) {
scriptExecutionService.createBatch(executionId, script.getId(), scheduleId, machineIds,
script.getPrivilegeLevel(),
effectiveTimeout(null, script.getDefaultTimeoutSeconds()),
initiatedBy, ExecutionSource.SCHEDULED);
initiatedBy, ExecutionSource.SCHEDULED, testModeEnabled);
}

// 3. Build the batched agent payload once — shared across every target machine. Per-script
Expand Down Expand Up @@ -245,7 +250,8 @@ private DispatchResponse dispatchBatch(String executionId, ScriptResponse script

// Persist the effective timeout per row so the watchdog can derive a
// per-execution stuck-threshold from it.
scriptExecutionService.createBatch(executionId, script.getId(), null, machineIds, privilegeLevel, timeoutSeconds, initiatedBy, source);
scriptExecutionService.createBatch(executionId, script.getId(), null, machineIds, privilegeLevel,
timeoutSeconds, initiatedBy, source, testModeEnabled);

List<String> args = ScriptArgsTokenizer.tokenize(argsOverride != null ? argsOverride : script.getDefaultArgs());
List<ScriptEnvVar> envVars = mergeEnvVars(script.getEnvVars(), envVarsOverride);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@
* later rename of the source {@code Script} is reflected in History without
* duplicating the name onto every row.
*/
public ScriptExecutionResponse create(String executionId,
String scriptId,
String machineId,
PrivilegeLevel privilegeLevel,
Integer timeoutSeconds,
String initiatedBy,
ExecutionSource source) {
ExecutionSource source,
boolean testScript) {

Check warning on line 73 in openframe-api-lib/src/main/java/com/openframe/api/service/rmm/script/ScriptExecutionService.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

Boolean testCommand/testScript parameter added to multiple method signatures instead of using an enum or parameter object

Boolean testCommand/testScript parameter added to multiple method signatures instead of using an enum or parameter object OFJAVA-028 forbids adding boolean parameters to method signatures because they say nothing at the call site and typically indicate the method does two things. This PR adds a raw boolean (testCommand / testScript) to createBatch, buildRunningRow, create, and createBatch across CommandExecutionService and ScriptExecutionService. Per the rule, this should be split into named methods (e.g. createBatch / createTestBatch) or use an enum (e.g. ExecutionOrigin.PRODUCTION / TEST) instead of a bare boolean flag threaded through many signatures.
// Single ad-hoc run (runScript) never originates from a schedule → scheduleId null.
List<ScriptExecution> saved = scriptExecutionRepository.saveRunning(RunningExecutionRows.builder()
.tenantId(tenantIdProvider.getTenantId())
Expand All @@ -80,6 +81,7 @@
.timeoutSeconds(timeoutSeconds)
.initiatedBy(initiatedBy)
.source(source)
.testScript(testScript)
.build());
log.info("Persisted execution row: executionId={} scriptId={} machineId={} initiatedBy={} source={} status=RUNNING",
executionId, scriptId, machineId, initiatedBy, source);
Expand All @@ -101,7 +103,8 @@
PrivilegeLevel privilegeLevel,
Integer timeoutSeconds,
String initiatedBy,
ExecutionSource source) {
ExecutionSource source,
boolean testScript) {
List<ScriptExecution> saved = scriptExecutionRepository.saveRunning(RunningExecutionRows.builder()
.tenantId(tenantIdProvider.getTenantId())
.executionId(executionId)
Expand All @@ -112,6 +115,7 @@
.timeoutSeconds(timeoutSeconds)
.initiatedBy(initiatedBy)
.source(source)
.testScript(testScript)
.build());
log.info("Persisted batch execution rows: executionId={} scriptId={} scheduleId={} machineCount={} initiatedBy={} source={} status=RUNNING",
executionId, scriptId, scheduleId, machineIds.size(), initiatedBy, source);
Expand Down Expand Up @@ -149,10 +153,11 @@
.timeoutSeconds(timeoutSeconds)
.initiatedBy(initiatedBy)
.source(source)
.packageManager(packageManager)
.packageName(packageName)
.softwareAction(softwareAction)
.testScript(false)
.build());

Check warning on line 160 in openframe-api-lib/src/main/java/com/openframe/api/service/rmm/script/ScriptExecutionService.java

View workflow job for this annotation

GitHub Actions / Flamingo Code Review

Software-install batch rows hardcode testScript(false) instead of honoring the test-mode flag

Software-install batch rows hardcode testScript(false) instead of honoring the test-mode flag In ScriptExecutionService, the new createBatch(...) overload accepts a `testScript` parameter that is threaded through everywhere except the software-install batch path a few lines below, which hardcodes `.testScript(false)`. If software-action executions are ever dispatched while test mode is enabled (or by the same monitoring pipeline that stamps scripts/commands as test), those rows will not be marked test, defeating the purpose of the flag for that execution path. Either thread the real flag through or add a comment clarifying software-install rows are never considered test executions intentionally.
Comment on lines 156 to 160

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.

🦩 🟠 [warn/recommended] Software-install batch rows hardcode testScript(false) instead of honoring the test-mode flag

In ScriptExecutionService, the new createBatch(...) overload accepts a testScript parameter that is threaded through everywhere except the software-install batch path a few lines below, which hardcodes .testScript(false). If software-action executions are ever dispatched while test mode is enabled (or by the same monitoring pipeline that stamps scripts/commands as test), those rows will not be marked test, defeating the purpose of the flag for that execution path. Either thread the real flag through or add a comment clarifying software-install rows are never considered test executions intentionally.

Evidence
                .packageManager(packageManager)
                .packageName(packageName)
                .softwareAction(softwareAction)
                .testScript(false)
                .build());
🤖 Prompt for AI agents
In openframe-api-lib/src/main/java/com/openframe/api/service/rmm/script/ScriptExecutionService.java around lines 156-160, address this code-review finding: Software-install batch rows hardcode testScript(false) instead of honoring the test-mode flag.
In ScriptExecutionService, the new createBatch(...) overload accepts a `testScript` parameter that is threaded through everywhere except the software-install batch path a few lines below, which hardcodes `.testScript(false)`. If software-action executions are ever dispatched while test mode is enabled (or by the same monitoring pipeline that stamps scripts/commands as test), those rows will not be marked test, defeating the purpose of the flag for that execution path. Either thread the real flag through or add a comment clarifying software-install rows are never considered test executions intentionally.
The flagged code:
```
                .packageManager(packageManager)
                .packageName(packageName)
                .softwareAction(softwareAction)
                .testScript(false)
                .build());
```
Make the minimal change that resolves the finding; do not refactor unrelated code.

confidence: 35 — react 👍/👎 to teach the reviewer

log.info("Persisted software batch rows: executionId={} scriptId={} packageManager={} packageName={} action={} machineCount={} initiatedBy={} source={} status=RUNNING",
executionId, scriptId, packageManager, packageName, softwareAction, machineIds.size(), initiatedBy, source);
return saved.stream().map(scriptExecutionMapper::toResponse).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ type ScriptSchedule implements Node {
updatedAt: Instant
"""The creating user (resolved from the internal createdBy id via the user DataLoader)."""
author: User
"""When true, this is a monitoring / QA fixture. Dispatch still runs, user-facing reads hide it.
Immutable at create-time."""
testScript: Boolean!
}

enum ScriptScheduleTrigger {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ type Script implements Node {
updatedAt: Instant
"""The creating user (resolved from the internal createdBy id via the user DataLoader). The raw createdBy id is not exposed — filter by author via authorIds."""
author: User
"""When true, this is a monitoring / QA fixture created by the monitoring pipeline. Immutable at
create-time. Set by the pipeline via createScript; hidden from user-facing list/facets/getById
reads (query-builder shield) — visible only on direct id lookup performed by the pipeline itself."""
testScript: Boolean!
}

type ScriptEnvVar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ void batchRunCommand_persistsPendingThenFansOut() {
// carrying the shared executionId + command/shell/privilege/timeout + initiatedBy.
verify(commandExecutionService).createBatch(
eq(response.getExecutionId()), eq("uptime"), eq(ScriptShell.BASH),
eq(machines), eq(PrivilegeLevel.ADMIN), eq(30), eq(INITIATED_BY));
eq(machines), eq(PrivilegeLevel.ADMIN), eq(30), eq(INITIATED_BY), eq(false));

// One publish per machine — every published payload must carry the FULL wire contract
// (executionId, code, shell, privilegeLevel, timeout), not just executionId+code, so a
Expand All @@ -242,7 +242,7 @@ void batchRunCommand_savesBeforePublishing() {

InOrder order = inOrder(commandExecutionService, commandNatsPublisher);
order.verify(commandExecutionService).createBatch(any(), any(), any(),
org.mockito.ArgumentMatchers.anyList(), any(), any(), any());
org.mockito.ArgumentMatchers.anyList(), any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean());
order.verify(commandNatsPublisher).publishCommand(eq("machine-1"), any(CommandMessage.class));
}

Expand All @@ -253,7 +253,7 @@ void batchRunCommand_dedupsMachineIds() {
commandDispatchService.batchRunCommand(batchInput(List.of("machine-1", "machine-1")), INITIATED_BY);

verify(commandExecutionService).createBatch(any(), any(), any(),
eq(List.of("machine-1")), any(), any(), any());
eq(List.of("machine-1")), any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean());
verify(commandNatsPublisher).publishCommand(eq("machine-1"), any(CommandMessage.class));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void createBatch_persistsOneRunningRowPerMachine() {
when(commandExecutionRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0));

service.createBatch(EXECUTION_ID, "uptime", ScriptShell.BASH, List.of("m-1", "m-2"),
PrivilegeLevel.ADMIN, 30, "alice");
PrivilegeLevel.ADMIN, 30, "alice", false);

@SuppressWarnings("unchecked")
ArgumentCaptor<List<CommandExecution>> captor = ArgumentCaptor.forClass(List.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ void runScript_persistsExecutionRowBeforeNatsPublish() {
eq(PrivilegeLevel.ADMIN),
eq(60), // effective timeout (script default, no override) — persisted for the watchdog
eq(USER_ID),
eq(ExecutionSource.MANUAL));
eq(ExecutionSource.MANUAL),
eq(false));
inOrder.verify(scriptNatsPublisher).publishScript(eq(MACHINE_ID), any(ScriptMessage.class));
}

Expand All @@ -163,7 +164,8 @@ void runScript_nullInitiatedBy_persistedAsNull() {
eq(PrivilegeLevel.ADMIN),
eq(60),
eq((String) null),
eq(ExecutionSource.MANUAL));
eq(ExecutionSource.MANUAL),
eq(false));
}

@Test
Expand Down Expand Up @@ -232,7 +234,7 @@ void runScript_persistsEffectiveTimeoutOnRow() {
scriptDispatchService.runScript(input, USER_ID, ExecutionSource.MANUAL);

verify(scriptExecutionService).create(
any(String.class), eq(SCRIPT_ID), eq(MACHINE_ID), eq(PrivilegeLevel.ADMIN), eq(90), eq(USER_ID), eq(ExecutionSource.MANUAL));
any(String.class), eq(SCRIPT_ID), eq(MACHINE_ID), eq(PrivilegeLevel.ADMIN), eq(90), eq(USER_ID), eq(ExecutionSource.MANUAL), eq(false));
assertThat(capturePublished().getTimeoutSeconds()).isEqualTo(90);
}

Expand Down Expand Up @@ -344,7 +346,8 @@ void batchRunScript_fansOutWithSharedExecutionId() {
eq(PrivilegeLevel.ADMIN),
eq(60),
eq(USER_ID),
eq(ExecutionSource.MANUAL));
eq(ExecutionSource.MANUAL),
eq(false));

ArgumentCaptor<ScriptMessage> captor = ArgumentCaptor.forClass(ScriptMessage.class);
for (String id : machines) {
Expand Down Expand Up @@ -398,7 +401,7 @@ void batchRunScript_dedupsMachineIds() {
scriptDispatchService.batchRunScript(batchInput(List.of("machine-1", "machine-1")), USER_ID, ExecutionSource.MANUAL);

verify(scriptExecutionService).createBatch(
any(), eq(SCRIPT_ID), eq((String) null), eq(List.of("machine-1")), eq(PrivilegeLevel.ADMIN), eq(60), eq(USER_ID), eq(ExecutionSource.MANUAL));
any(), eq(SCRIPT_ID), eq((String) null), eq(List.of("machine-1")), eq(PrivilegeLevel.ADMIN), eq(60), eq(USER_ID), eq(ExecutionSource.MANUAL), eq(false));
verify(scriptNatsPublisher, times(1)).publishScript(eq("machine-1"), any(ScriptMessage.class));
}

Expand Down
Loading
Loading