Skip to content

feat(drivers): repo-level defaultDriver in .swamp.yaml (swamp-club#159)#1219

Merged
stack72 merged 1 commit intomainfrom
feat/repo-default-driver
Apr 24, 2026
Merged

feat(drivers): repo-level defaultDriver in .swamp.yaml (swamp-club#159)#1219
stack72 merged 1 commit intomainfrom
feat/repo-default-driver

Conversation

@keeb
Copy link
Copy Markdown
Contributor

@keeb keeb commented Apr 24, 2026

Summary

Closes swamp-club#159. Adds a repo-level defaultDriver (and defaultDriverConfig) to .swamp.yaml so a repo can declare a baseline execution driver once, without touching every workflow or passing --driver on every CLI invocation.

New resolution chain for workflow steps:

cli > step > job > workflow > definition > repo > "raw"
  • RepoMarkerData gains two optional fields (defaultDriver, defaultDriverConfig) — same shape as the existing datastore field.
  • resolveDriverConfig gains an optional 6th repo tier, positioned below definition and above the "raw" fallback.
  • WorkflowExecutionService loads .swamp.yaml once per run() (memoized on the instance so nested workflow calls don't re-read) and passes the repo tier into resolveDriverConfig at the step-context build site.
  • design/execution-drivers.md updated to document the new tier with an example.

Scope: workflow execution only. Direct swamp model method run invocations go through a separate code path that doesn't call resolveDriverConfig today and will still default to "raw" when no CLI override is supplied. Flagged in the adversarial review as ADV-1; can be a follow-up if desired.

Test Plan

  • deno check — passes
  • deno lint — passes
  • deno fmt --check — passes
  • deno run test — 4732 passed / 0 failed / 1 ignored
  • deno run compile — produces a working binary

New tests added:

  • 5 cases in driver_resolution_test.ts covering repo-tier precedence (wins over default, loses to workflow/job/step/cli, skipped when driver undefined but driverConfig set)
  • 1 roundtrip in repo_marker_repository_test.ts with defaultDriver + defaultDriverConfig
  • 3 service-level tests in execution_service_test.ts with a real .swamp.yaml on disk: default applied, CLI overrides, no default → raw

…b#159)

Adds optional defaultDriver and defaultDriverConfig fields to RepoMarkerData
and wires them into resolveDriverConfig as a new 'repo' tier below the
definition slot. New chain:

  cli > step > job > workflow > definition > repo > "raw"

The repo tier lets repos declare a baseline driver once in .swamp.yaml
without setting driver: on every workflow or passing --driver on every
CLI invocation. Mirrors the existing RepoMarkerData.datastore pattern.

WorkflowExecutionService loads the marker once per run() (memoized on the
instance so nested workflow calls don't re-read) and passes the repo tier
into resolveDriverConfig at the step-context build site.

Scope: this covers workflow execution. Direct 'swamp model method run'
invocations go through a separate code path that doesn't call
resolveDriverConfig today and will still default to 'raw' when no CLI
override is supplied.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Code Review

Well-crafted PR. The new repo-level defaultDriver tier is cleanly integrated into the existing resolution chain, with good test coverage at every layer.

Blocking Issues

None.

Suggestions

  1. Positional parameter growth on resolveDriverConfig (src/domain/drivers/driver_resolution.ts:44-51): The function now has 6 optional DriverSource parameters, all the same type. Call sites like execution_service.ts:1843-1853 are fragile — passing undefined for definition to get to repo is easy to misread. A named-fields object ({ cli?, step?, job?, workflow?, definition?, repo? }) would make call sites self-documenting. Not for this PR, but worth considering as a follow-up since the parameter list is only going to grow with custom tiers.

  2. execute() doesn't forward driver (execution_service.ts:1389-1405): The convenience wrapper only forwards a subset of run() options. This is pre-existing, but the new tests highlight the gap — the "CLI driver overrides" test must use run() directly while the other two use execute(). If more options get added over time, it might be simpler to have execute() accept the full run() options type.

What looks good

  • Memoization pattern for the repo marker is solid — lazy load with undefined/null sentinel, loaded once per service instance, shared across nested workflows.
  • Test coverage is thorough: 5 unit cases for resolution precedence, 1 persistence roundtrip, and 3 service-level integration tests exercising the default-applied, CLI-override, and no-default-fallback paths.
  • Design doc updated with the new tier and example .swamp.yaml.
  • The undefined for definition at the call site correctly preserves pre-existing behavior where that tier wasn't populated.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Adversarial Review

Critical / High

None found. The changes are minimal, well-scoped, and follow established patterns.

Medium

  1. Race in loadRepoMarker() memoizationsrc/domain/workflows/execution_service.ts:1169-1175

    If two concurrent run() calls hit loadRepoMarker() before either completes, both see this.repoMarker === undefined, both issue a file read, and both write the result. Since both reads return the same data this is functionally harmless — but it means the "read at most once" contract in the comment is aspirational, not guaranteed. In practice, WorkflowExecutionService instances are typically short-lived and sequential, so this is unlikely to matter.

    Breaking scenario: Two concurrent run() calls → two file reads instead of one. No wrong result, just wasted I/O.

    Fix (if desired): Store the promise itself: this.markerPromise ??= this.markerRepo.read(RepoPath.create(this.repoDir)) and await the stored promise on subsequent calls.

  2. definition tier is always undefined at the call sitesrc/domain/workflows/execution_service.ts:1848

    The function signature documents a definition tier between workflow and repo, but the call site passes undefined for it. This is pre-existing (before this PR, only 4 args were passed, leaving definition as undefined by default), but the PR makes it more visible by passing undefined explicitly. Model-level driver settings from definitions are not respected during workflow execution — the repo tier can't compensate for that gap.

    Not introduced by this PR; just surfaced.

Low

  1. No runtime validation of defaultDriver / defaultDriverConfig typessrc/infrastructure/persistence/repo_marker_repository.ts:105

    parseYaml(content) as RepoMarkerData is a type assertion without runtime validation. A .swamp.yaml with defaultDriver: 123 (number instead of string) would survive parsing and reach resolveDriverConfig, where source.driver !== undefined would be true for the number 123, and it would be returned as the driver value — a string-typed field carrying a number. This is the pre-existing pattern for all RepoMarkerData fields and not introduced by this PR.

Verdict

PASS — Clean, well-tested addition that follows existing patterns. The new repo tier slots into the resolution chain correctly, memoization is sound for practical usage, and test coverage is thorough (unit precedence tests + integration tests with real .swamp.yaml on disk). The medium findings are either pre-existing or cosmetic concurrency — nothing blocks merge.

@stack72 stack72 merged commit 76b653b into main Apr 24, 2026
10 checks passed
@stack72 stack72 deleted the feat/repo-default-driver branch April 24, 2026 17:10
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