Skip to content

Add stepOptions to probitas.json configuration#93

Merged
lambdalisue merged 3 commits intomainfrom
feat/step-options-config
Jan 7, 2026
Merged

Add stepOptions to probitas.json configuration#93
lambdalisue merged 3 commits intomainfrom
feat/step-options-config

Conversation

@lambdalisue
Copy link
Member

Summary

  • Add stepOptions field to probitas.json for global default step options
  • Support timeout and retry configuration at config level
  • Thread stepOptions through CLI → subprocess → Runner pipeline
  • Add comprehensive validation and tests

Why

Previously, users had to specify step options (timeout, retry) on every step or scenario, leading to repetition. This change allows setting defaults in probitas.json, reducing boilerplate while maintaining the ability to override at scenario or step level.

The priority chain ensures flexibility: step options > scenario.stepOptions > config.stepOptions > builder defaults.

Implementation uses heuristic comparison against DEFAULT_* constants in Runner's StepRunner, since Builder resolves all undefined values to concrete defaults before execution.

Test Plan

  • Config validation tests pass (partial stepOptions, type validation)
  • Integration tests verify CLI → subprocess → Runner propagation
  • All existing tests continue to pass
  • Example config updated with stepOptions documentation

Enables global default step options (timeout, retry) configuration
through probitas.json, reducing repetition across scenarios.

Configuration priority: step options > scenario.stepOptions >
config.stepOptions > builder defaults.

When steps use default values, config stepOptions are applied via
heuristic comparison in Runner's StepRunner. This approach works
because Builder resolves all undefined values to concrete defaults,
so Runner compares against DEFAULT_* constants to detect when to
apply config overrides.

Changes:
- Add stepOptions field to ProbitasConfig with validation
- Thread stepOptions through CLI → subprocess → Runner
- Update IPC protocol to include stepOptions
- Add comprehensive config loading tests
- Update example config with stepOptions documentation
Copilot AI review requested due to automatic review settings January 7, 2026 13:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for configuring default step options (timeout and retry behavior) in the probitas.json configuration file, eliminating the need to specify these options on every step or scenario.

Key Changes:

  • Adds stepOptions field to probitas.json for global default step timeout and retry configuration
  • Implements validation for stepOptions with comprehensive type checking
  • Threads stepOptions from CLI through subprocess to Runner execution pipeline

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/cli/types.ts Adds StepOptions type import and stepOptions field to ProbitasConfig interface
src/cli/config.ts Implements validation for stepOptions including nested retry configuration using type guards
src/cli/config_test.ts Adds comprehensive tests for stepOptions loading and validation including partial configurations
src/cli/commands/run.ts Extracts stepOptions from config and passes it through subprocess execution pipeline
src/cli/_templates/run_protocol.ts Updates RunCommandInput interface to include optional stepOptions parameter
src/cli/_templates/run.ts Passes stepOptions from subprocess input to Runner.run() method
assets/probitas.jsonc Documents stepOptions configuration with examples for timeout and retry settings
deno.json Updates dependencies to newer versions of @probitas packages that support stepOptions
deno.lock Reflects dependency version updates in lockfile
.gitignore Corrects path pattern by removing unnecessary leading ./
Comments suppressed due to low confidence (1)

src/cli/types.ts:12

  • The JSDoc comment is outdated and incorrect. It states configuration is loaded from "deno.json/deno.jsonc" and stored in the "probitas" section, but the actual implementation loads from standalone probitas.json/probitas.jsonc files at the root level (not nested). Update the comment to reflect that this is "Configuration loaded from probitas.json/probitas.jsonc" and remove the mention of the "probitas" section.
 * ProbitasConfig - Configuration loaded from deno.json/deno.jsonc
 *
 * Configuration is stored in the "probitas" section of deno.json.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +320 to +452

it("loads stepOptions configuration", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
timeout: 60000,
retry: {
maxAttempts: 3,
backoff: "exponential",
},
},
}),
);

const config = await loadConfig(configPath);

assertEquals(config.stepOptions, {
timeout: 60000,
retry: {
maxAttempts: 3,
backoff: "exponential",
},
});
});

it("loads partial stepOptions with only timeout", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
timeout: 45000,
},
}),
);

const config = await loadConfig(configPath);

assertEquals(config.stepOptions, {
timeout: 45000,
});
});

it("loads partial stepOptions with only retry", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
retry: {
maxAttempts: 5,
},
},
}),
);

const config = await loadConfig(configPath);

assertEquals(config.stepOptions, {
retry: {
maxAttempts: 5,
},
});
});

it("validates stepOptions.timeout must be number", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
timeout: "30s",
},
}),
);

await assertRejects(
async () => await loadConfig(configPath),
Error,
);
});

it("validates stepOptions.retry.backoff values", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
retry: {
backoff: "invalid",
},
},
}),
);

await assertRejects(
async () => await loadConfig(configPath),
Error,
);
});

it("validates stepOptions.retry.maxAttempts must be number", async () => {
await using sbox = await sandbox();

const configPath = sbox.resolve("probitas.json");
await Deno.writeTextFile(
configPath,
JSON.stringify({
stepOptions: {
retry: {
maxAttempts: "3",
},
},
}),
);

await assertRejects(
async () => await loadConfig(configPath),
Error,
);
});
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for an empty stepOptions object (stepOptions: {}) to verify the configuration loader handles it correctly. While PartialOf should handle this, an explicit test would ensure the behavior is clear and prevent regressions.

Copilot uses AI. Check for mistakes.
@@ -19,6 +19,18 @@
"maxFailures": 0,
// Default timeout for each scenario
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The comment "Default timeout for each scenario" on line 20 should clarify that this timeout is for the entire scenario execution, not individual steps. The new stepOptions.timeout (line 24-25) is for individual step timeouts. Consider updating to "Default timeout for each scenario execution" or "Default timeout for scenario completion" to avoid confusion with the step-level timeout.

Suggested change
// Default timeout for each scenario
// Default timeout for each scenario execution (overall scenario completion)

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +28
// Step timeout in milliseconds (default: 30000)
"timeout": 30000,
// Retry configuration for transient failures
"retry": {
// Maximum retry attempts (1 = no retry, 2 = one retry, etc.)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The comment states "default: 30000" for the timeout, but this is a configuration example where users explicitly set values. If 30000 is indeed the default when not specified, consider clarifying this as "recommended: 30000" or removing the default reference since this is the configuration file where users override defaults. The same applies to the retry.maxAttempts comment on line 29.

Suggested change
// Step timeout in milliseconds (default: 30000)
"timeout": 30000,
// Retry configuration for transient failures
"retry": {
// Maximum retry attempts (1 = no retry, 2 = one retry, etc.)
// Step timeout in milliseconds (recommended: 30000)
"timeout": 30000,
// Retry configuration for transient failures
"retry": {
// Example: Maximum retry attempts (1 = no retry, 2 = one retry, etc.)

Copilot uses AI. Check for mistakes.
@lambdalisue lambdalisue merged commit 4f748bd into main Jan 7, 2026
10 checks passed
@lambdalisue lambdalisue deleted the feat/step-options-config branch January 7, 2026 13:26
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