fix(models): lenient GlobalArgsSchema validation for direct execution#1399
Conversation
…tion (swamp-club#366) Direct execution creates ephemeral model instances where not all globalArgs are needed — get/sync/delete don't require creation-time fields. Previously, strict GlobalArgsSchema validation blocked these calls with "expected string, received undefined" for models like bucket-policy that have required fields in their upstream schema. Apply .partial() at three validation points so provided fields are still type-checked but missing fields are allowed. swamp model create retains strict validation via its own safeParse in create.ts. Also reframes direct execution as the default approach in the swamp-model and swamp-workflow skills, since model create is only needed for persistent definitions with CEL expressions or shared config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
None.
Verdict
PASS — this PR has no user-facing UX changes. The three modified source files (direct_execution.ts, validation_service.ts, method_execution_service.ts) change internal validation logic only; no CLI flags, help text, output formats, or error message templates are added or altered. The behavioral effect is strictly positive from a UX standpoint: users running get on models with required GlobalArgsSchema fields (e.g. @swamp/aws/s3/bucket-policy) no longer receive spurious validation errors. Invalid types on provided fields still produce the same clear Invalid global arguments for type '...' error message as before. The skill documentation changes are internal Claude guidance, not user-visible CLI output.
There was a problem hiding this comment.
Code Review
Clean, well-tested fix. The .partial() approach correctly relaxes required-field enforcement for direct execution while preserving type validation on provided fields. The three validation points are consistent, model create retains strict validation in create.ts:183, and the 6 new tests cover both the happy path (empty required args pass) and error path (wrong types still rejected).
Suggestions
-
Duplicated
.partial()guard logic: The pattern"partial" in schema && typeof schema.partial === "function" ? schema.partial() : schemaappears identically indirect_execution.ts,validation_service.ts, andmethod_execution_service.ts. A small shared helper (e.g.toLenientSchema(schema: z.ZodTypeAny): z.ZodTypeAny) would reduce the duplication and make the intent clearer at each call site. -
swamp model validatealso becomes lenient: Thevalidation_service.tschange affects all callers ofvalidateModel, including theswamp model validatecommand for persistent definitions. This means manually editing a YAML to remove a required global arg would no longer be caught bymodel validate. This may be intentional (the PR description argues the old validation was "binary: all-optional = pass everything, any required = block everything"), but worth noting explicitly if not already considered.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None.
Medium
-
validation_service.ts:511-514—.partial()weakensswamp model validatefor persisted definitions.
ThevalidateGlobalArgumentsmethod is called fromvalidateModel, which serves both direct/workflow execution (execution_service.ts:469) and theswamp model validateCLI command (validate.ts:219, 286). The.partial()change is correct for direct execution, but it also meansswamp model validatewill no longer catch missing required globalArgs in persisted definitions — e.g., if a user manually edits a YAML file and deletes theBucketfield from abucket-policydefinition,swamp model validatewill now report it as passing.Breaking example: Create a definition with
model createproviding all required fields. Manually edit the YAML to removeBucket. Runswamp model validate. Before this PR: fails. After: passes.Impact: Low in practice since
model createenforces strict validation on creation, and manual YAML editing is an edge case. Butswamp model validateexists specifically to catch this kind of drift.Suggested fix: Thread a
lenient: booleanoption throughvalidateGlobalArguments(or throughvalidateModel), defaulting totruefor workflow/direct-execution callers andfalsefor the validate command. Alternatively, accept the trade-off and document it.
Low
- All three files — Models with
ZodEffectsglobalArgs won't benefit from lenient validation.
The duck-typing guard"partial" in schema && typeof schema.partial === "function"correctly falls back to strict validation forZodEffects(from.refine()/.transform()wrappers). This means if a model wraps itsglobalArgumentsschema with a cross-field refinement, direct execution will still be blocked by required field validation. This is a safe fallback — not a bug — but models using refinements won't get the fix. Documenting this limitation would help future debugging.
Verdict
PASS — The core logic is correct: .partial() validates provided field types while allowing omissions, model create retains strict validation via its own safeParse, and all three validation points are updated consistently. The 6 new tests cover the key cases (empty required globalArgs passes, wrong types still rejected). The medium finding about swamp model validate is a valid trade-off that should be considered but doesn't block merge.
Summary
.partial()on GlobalArgsSchema at three validation points in the direct execution / workflow execution pipeline so missing globalArgs fields are allowed while provided fields are still type-checkedswamp model createretains strict validation (its ownsafeParseincreate.tsis untouched)Context
Closes swamp-club#366. Models like
@swamp/aws/s3/bucket-policyhave required fields (Bucket,PolicyDocument) in theirGlobalArgsSchemabecause the upstream CloudFormation schema declares them required. This blocked workflow YAML direct execution ofget— the method doesn't need those fields, but the validation rejected the empty globalArgs before the method could run.Direct execution already had no useful GlobalArgsSchema validation — models with all-optional fields (like
@swamp/aws/s3/bucket) passed{}without checking anything. The validation was binary: all-optional = pass everything, any required = block everything.Changes
direct_execution.ts.partial()on GlobalArgsSchema when auto-creating definitionsvalidation_service.ts.partial()in pre-execution model validationmethod_execution_service.ts.partial()at method execution timedirect_execution_test.tsvalidation_service_test.tsmethod_execution_service_test.tsswamp-model/SKILL.mdmodel createas exceptionswamp-model/references/direct-execution.mdswamp-workflow/SKILL.mdswamp-workflow/references/direct-execution.mdTest plan
bucket-policy getnow passes all three validation gates🤖 Generated with Claude Code