fix: unify workflow step execution with direct method run#964
Conversation
Workflow step execution (`execution_service.ts:executeModelMethod`) and direct CLI execution (`modelMethodRun`) both call `executionService.executeWorkflow()` but diverged in context population, report handling, and check control. This caused features added to the CLI path to silently not apply to workflow-triggered method runs. Closes the five specific divergences: 1. **Failed-execution reports**: Workflow steps now run `@swamp/method-summary` reports on failure (with `executionStatus: "failed"` and `errorMessage`), matching the CLI path behavior. 2. **`swampSha` in report context**: Threaded through `WorkflowRunInput` → `StepExecutionContext` → `MethodReportContext` so workflow-triggered reports include the swamp git SHA. 3. **`outputSpecs` in report context**: Extracted `buildOutputSpecs` to a shared domain module (`output_spec_builder.ts`) and call it in both paths, so workflow reports include model output spec schemas. 4. **Check skip options**: Added `--skip-checks`, `--skip-check <name>`, and `--skip-check-label <label>` flags to `workflow run`. Options are threaded through `WorkflowRunInput` → `StepExecutionContext` → `MethodContext`. 5. **`buildOutputSpecs` layer violation**: Extracted from the libswamp layer (`src/libswamp/models/run.ts`) to the domain layer (`src/domain/models/output_spec_builder.ts`) so both the CLI and workflow paths can use it without violating dependency direction. Fixes #947 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
None.
Verdict
PASS — The three new flags (--skip-checks, --skip-check <name>, --skip-check-label <label>) added to workflow run are identical in name, description, and behavior to the existing flags on model method run, which is exactly what you want for parity. Help text is clear, flags use collect correctly, JSON output is unaffected (these are execution control flags, not output fields), and both log and JSON output modes are already handled by the existing renderer. No UX regressions visible.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
Degraded
zodToJsonSchemafallback insrc/domain/models/output_spec_builder.ts:28-33The new
zodToJsonSchemafunction catches conversion failures and falls back to{ type: "object" }. The original insrc/libswamp/types/schema_helpers.ts:175-181falls back to a comprehensivemanualZodToJsonSchemathat handles strings, numbers, booleans, arrays, records, enums, literals, and unknowns. If a model usesz.record(z.unknown())orz.array(z.string())as a resource schema — types known to crashz.toJSONSchema()per the comment on line 102 ofschema_helpers.ts— the new code will silently produce{ type: "object" }instead of an accurate schema representation. This affects report context data integrity for consumers that rely onoutputSpecs[].schema.Breaking example: A model with
resources: { data: { schema: z.record(z.unknown()), ... } }will produce{ type: "object" }from the workflow path but{ type: "object", additionalProperties: {} }from the CLI path (which still uses the oldschema_helpers.tszodToJsonSchema). Report consumers comparing CLI vs workflow output specs will see different schemas.Suggested fix: Import and reuse
zodToJsonSchemafrom../../libswamp/types/schema_helpers.tsinstead of duplicating it with a weaker fallback — or move the comprehensive version to the domain layer alongsidebuildOutputSpecs. -
Ratchet bump to
KNOWN_MUTUAL_DEPENDENCIES = 11inintegration/architecture_boundary_test.ts:134The PR description states it fixes a layer violation by moving
buildOutputSpecsfrom libswamp to domain, but it introduces a new mutual dependency between bounded contexts in the process (bumping the ratchet from 10 to 11). The PR description mentions "Layer violation fix" but the net effect is trading one cross-boundary dependency for another. Worth confirming the new cycle is the intendeddomain/models → domain/reports(forOutputSpecInfotype import) and not something unintentional.
Low
-
reportRegistry.getAll().length > 0called twice in the error path (src/domain/workflows/execution_service.ts:756) —getAll()builds a fresh array on each call. Not a correctness issue, but on the success path (line 598) it's also called this way. Minor inefficiency if the registry is large. -
No model-scope reports on failure (
src/domain/workflows/execution_service.ts:826) — The success path runs both method-scope and model-scope reports (lines 700-725). The failure path only runs method-scope reports. This matches the CLI failure path inrun.ts:488which also only runs method-scope, so it's consistent — but worth noting that model-scope reports never see failures in either path.
Verdict
PASS — The changes are structurally sound, properly thread context through the full workflow chain, and maintain consistency with the existing CLI path. The zodToJsonSchema fallback divergence (Medium #1) is the most actionable finding — it creates a subtle behavioral difference between the two paths that this PR is specifically trying to unify. The architecture ratchet bump deserves a quick confirmation. No blocking issues found.
There was a problem hiding this comment.
Code Review
Blocking Issues
None — this PR is well-structured and addresses a real parity gap between the CLI and workflow execution paths.
Suggestions
-
zodToJsonSchemafallback divergence (src/domain/models/output_spec_builder.ts:28-33): The newzodToJsonSchemafalls back to{ type: "object" }on failure, while the original insrc/libswamp/types/schema_helpers.ts:175-181falls back to a comprehensivemanualZodToJsonSchemathat handles records, arrays, enums, literals, etc. This means workflow-path reports will get degraded schema info whenz.toJSONSchema()fails (e.g.,z.record(z.unknown())). Consider importing and reusing the existing fallback, or extracting the manual converter to the domain layer alongsidebuildOutputSpecs. -
New
models ↔ reportsmutual dependency (ratchet 10→11): The extraction ofbuildOutputSpecsintosrc/domain/models/creates a new circular dependency because it importsOutputSpecInfofromsrc/domain/reports/report_context.ts. One option to avoid this would be to defineOutputSpecInfoin a shared types module or in the models bounded context (since it describes model output structure). Not blocking since the ratchet mechanism explicitly tracks this, but worth considering as a follow-up to keep the dependency count from growing. -
Large duplicated report execution block (
src/domain/workflows/execution_service.ts:754-848): The ~95-line block for running reports on failed executions closely mirrors the success-path report block (~30 lines above). Consider extracting a shared helper to reduce duplication and ensure both paths stay in sync. Not blocking since the code is correct and tested.
Summary
Closes #947. Workflow step execution and direct
swamp model method runused different code paths to invoke model methods. This caused features added to the CLI path (report context fields, failure reports, check control) to silently not apply to workflow-triggered runs.This PR closes five specific divergences without introducing new abstractions — just targeted fixes to existing files:
@swamp/method-summaryreports on failure (withexecutionStatus: "failed"anderrorMessage), matching CLI behaviorswampShain report context: Threaded through the full workflow chain so reports include the swamp git SHAoutputSpecsin report context: ExtractedbuildOutputSpecsto a shared domain module and call it in both paths--skip-checks,--skip-check,--skip-check-labelflags toworkflow run, threaded through toMethodContextbuildOutputSpecsfrom libswamp to domain layer (output_spec_builder.ts)User Impact
swampShaandoutputSpecs@swamp/method-summarywith error details--skip-checks,--skip-check <name>,--skip-check-label <label>flags onworkflow runTest Plan
Automated
output_spec_builder_test.tstests (empty/resource/file/both specs)execution_service_test.tstest (check skip + swampSha context propagation)run_test.tstests pass (buildOutputSpecs extraction didn't break CLI path)execution_service_test.tstests passdeno fmt --checkcleandeno lintcleandeno checkcleanManual smoke tests (compiled binary, fresh repo)
model method run sync— baseline works, reports includeswampSha+outputSpecs✅syncstep — reports now includeswampSha+outputSpecs(previously missing) ✅allowFailure: true) —@swamp/method-summaryreport runs withstatus: "failed"and error message (previously no reports on failure) ✅workflow run --skip-check-label live— correctly skips checks by label (previously not possible) ✅workflow run --skip-checks— correctly skips all checks (previously not possible) ✅workflow run --skip-check label-not-banned— correctly skips specific check by name (previously not possible) ✅Out of scope
detectEnvVarUsageInDefinition) in workflow path — workflow authors control their expression context🤖 Generated with Claude Code