feat: add pre-flight checks for model method execution#697
Conversation
Add a `checks` field to `ModelDefinition` — a `Record<string, CheckDefinition>` that follows the same pattern as `methods`, `resources`, and `files`. Checks run automatically before mutating method execution (create/update/delete/action) and are skipped for read/list operations. Each check has an `execute(context: MethodContext) => Promise<CheckResult>` signature, receiving the same context as methods (globalArgs, repos, repoDir, logger) but without writeResource/createFileWriter — checks are read-only. The open signature supports value validation, cross-model queries, live API checks, OPA evaluation, or any future check type without framework changes. Core changes: - CheckResult, CheckDefinition interfaces and isMutatingKind() helper in model.ts - Pre-flight check execution in method_execution_service.ts executeWorkflow() with filtering by appliesTo, skipCheckNames, skipCheckLabels, skipAllChecks - Validation service integration: model validate runs checks when checkContext is provided, with --label and --method filtering flags - User model loader passes through checks from extension model TypeScript files - CLI flags: --skip-checks, --skip-check <name>, --skip-check-label <label> on model method run; --label <label>, --method <method> on model validate Design decisions: - All checks run sequentially (may depend on shared state), all failures collected before aborting — users see every problem at once - Labels are free-form strings, not an enum — model authors choose vocabulary - appliesTo scoping lets checks target specific methods (e.g., create-only) - CheckValidationContext uses MethodContext["dataRepository"] type to avoid adding new domain→infrastructure import violations This is the foundation layer. Two planned follow-ups: - Extension checks: allow adding checks to existing model types via extensions - Definition-level checks: CEL expression-based checks in YAML definition files Co-Authored-By: Claude Opus 4.6 <[email protected]>
Adds two capabilities on top of the pre-flight checks foundation:
## Extension Checks
Extension authors can now add pre-flight checks to existing model types
via `modelRegistry.extend()`. The method accepts an optional third
`checks` parameter:
```typescript
modelRegistry.extend("aws/ec2/vpc", {}, {
"no-cidr-overlap": {
description: "Ensure CIDR does not overlap",
labels: ["policy"],
execute: async (context) => { /* ... */ return { pass: true }; },
},
});
```
Check name conflicts with existing checks throw an error at registration
time, following the same immutable merge pattern used for methods.
## Definition-Level Check Selection
YAML definition authors can now control which checks run for their
specific definition using `checks.require` and `checks.skip`:
```yaml
checks:
require:
- no-dangerous-commands # immune to --skip-checks CLI flags
skip:
- slow-api-check # always skipped for this definition
```
Semantics:
- `skip` always wins (even over `require` if both list the same check)
- `require` checks are immune to `--skip-checks`, `--skip-check <name>`,
and `--skip-check-label <label>` CLI flags
- `require` checks still respect `appliesTo` method scoping
- `model validate` honors definition-level `skip` lists
- Validation fails if `require` or `skip` references a check name that
does not exist on the model type
- Validation warns when the same check appears in both lists
## Domain Changes
- `Definition` entity: added `CheckSelection` type (`require/skip`
string arrays), `CheckSelectionSchema` Zod schema, `checkSelection`
accessor, threaded through `create()`, `fromData()`,
`withUpgradedGlobalArguments()`, and `toData()`
- `ModelRegistry.extend()`: accepts optional `checks` parameter with
name conflict detection
- `executeWorkflow()`: check filtering respects definition-level
require/skip before CLI skip flags
- `ValidationService`: added `validateCheckSelection()` private method,
updated `runCheckValidations()` to honor definition-level skip lists
- `UserModelLoader`: extensions can export `checks` array alongside
`methods` array
## Tests (17 new)
- model_test.ts (4): extend with checks, check name conflict, preserve
existing checks when adding methods, merge checks from extension
- method_execution_service_test.ts (5): required check immune to
skipAllChecks, required immune to skipCheckNames, definition skip
always wins, skip wins over require, required respects appliesTo
- validation_service_test.ts (8): no selection passes, valid require,
nonexistent require fails, nonexistent skip fails, require+skip
overlap warning, valid skip, definition skip excludes from validate
run, model without checks
## End-to-End Verification
Tested manually with an extension adding 3 checks to `command/shell`:
- Extension checks load and appear in `model validate --json`
- Dangerous command blocked by `no-dangerous-commands` check
- `--skip-checks` bypasses all non-required checks
- `--skip-check <name>` skips specific check
- `--skip-check-label <label>` skips by label
- `checks.require` makes check immune to all CLI skip flags
- `checks.skip` permanently excludes check from execute and validate
- Invalid check selection caught by validation
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- swamp-troubleshooting: add troubleshooting for check selection errors (nonexistent checks, require/skip overlap, extension conflicts, required check won't skip) - swamp-extension-model: fix checks format to array-of-records pattern matching methods, clarify conflict rules - swamp-model: clarify check selection precedence rules with explicit CLI flag names and override behavior Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
This allows us to do this: CLI usage: If you want to encode the checks in the definition: |
Move CheckDefinition interface, CheckResult, labels convention, appliesTo scoping, skip CLI flags, and extension checks documentation from SKILL.md to references/checks.md to reduce SKILL.md from 611 to 533 lines. Co-Authored-By: Claude Opus 4.6 <[email protected]>
671c89b to
843c5b7
Compare
Without this, the new references/checks.md file would not be copied to repos during `swamp repo init` or `swamp repo upgrade`. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Three fixes from adversarial review: 1. Checks with appliesTo are now skipped during `model validate` when no --method is specified, preventing confusing failures from checks that only make sense for specific methods. 2. Invalid check return values (undefined, null, missing pass field) are now caught defensively instead of crashing, producing a clear "invalid result" error message. 3. appliesTo arrays are now validated against existing methods on the model — a typo like appliesTo: ["creat"] will surface as a validation warning instead of silently never running. Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
Code Review: Pre-flight Checks for Model Method Execution
Overall Assessment
This is a well-designed and well-implemented feature. The PR adds pre-flight checks that run before mutating model methods, with comprehensive support for extension checks, definition-level check selection, and CLI flags for filtering.
No Blocking Issues Found
Code Quality
- TypeScript strict mode: All code follows strict TypeScript patterns. The
anytypes in CLI commands are properly documented with lint-ignore comments and represent pragmatic workarounds for Cliffy's type inference limitations. - Named exports: All exports are named exports as required.
- License headers: All files include the AGPLv3 copyright header.
Test Coverage
- 17 new unit tests covering all check behaviors (pass/fail, filtering, skip flags, extension merging, definition-level selection)
- Integration tests updated for new validation count (4 → 5)
- Tests are located next to source files as required
Domain-Driven Design
The implementation follows DDD principles correctly:
CheckDefinitionandCheckResultare properly modeled as value objectsCheckSelectionis appropriately stored as part of the Definition entityValidationServiceandMethodExecutionServiceare correctly implemented as domain services
Security
No security vulnerabilities identified. The checks feature is a validation layer that runs before method execution with proper input validation.
Suggestions (Non-Blocking)
-
Consider future enhancement: The
CheckValidationContextcreates a mock logger inline (lines 317-325 in validation_service.ts). This works but could be extracted to a test utility in the future for consistency. -
Documentation: The skill docs updates are comprehensive and the new
references/checks.mdis properly included in bundled skills.
Summary of Changes Reviewed
model.ts: CheckResult, CheckDefinition interfaces, isMutatingKind() helper, checks field on ModelDefinitionmethod_execution_service.ts: Pre-flight check execution with proper filteringvalidation_service.ts: Check validation including appliesTo validation against methodsdefinition.ts: CheckSelection type and checkSelection accessoruser_model_loader.ts: Support for checks in model and extension exports- CLI commands: New flags for skipping checks and filtering
- Tests: Comprehensive unit test coverage
LGTM! Approving.
There was a problem hiding this comment.
Adversarial Review
I've systematically attempted to break this code across logic, error handling, security, concurrency, data integrity, resource management, and API contracts.
Critical / High
None found. The implementation is solid.
Medium
-
Empty
appliesToarray silently disables check (src/domain/models/method_execution_service.ts:296)- If a check has
appliesTo: [](empty array), the truthy check passes but[].includes(methodName)always returns false - Breaking example: A model author accidentally writes
appliesTo: []instead of omitting the field — the check silently never runs - Suggested fix: Add validation in
validateCheckSelection()to warn whenappliesTois an empty array, or document this behavior explicitly - Mitigating factor: Unlikely to happen accidentally; the validation already checks that appliesTo references existing methods
- If a check has
-
Check execution order non-deterministic across extension loads (
src/domain/models/method_execution_service.ts:291)Object.entries(modelDef.checks)returns checks in insertion order, but when checks come from base model + extension, order depends on when extensions were loaded- Breaking example: If extension loading order varies between runs (parallel file discovery, etc.), checks might run in different orders
- Mitigating factor: Checks should be stateless and independent, so order shouldn't matter. The PR correctly documents "checks run sequentially" without guaranteeing order.
Low
-
Empty methodName in validation context (
src/domain/models/validation_service.ts:315)- When
--methodis not provided,methodName: checkContext.method ?? ""passes empty string to checks - Mitigating factor: Checks with
appliesToare already skipped when no method is provided (line 294-300), so only checks withoutappliesTorun, and they typically don't rely on methodName
- When
-
No test for definition-level checks round-trip through toData/fromData (
src/domain/definitions/definition_test.ts)- The existing tests confirm
checks: undefinedin toData output, but no test exercises a definition with actual check selection through the round-trip - Mitigating factor: The schema is validated via Zod, and structuredClone ensures deep copy
- The existing tests confirm
Verdict
PASS — The pre-flight checks implementation is well-designed with comprehensive error handling. The defensive check result validation (line 334) properly catches malformed check returns. The filtering logic correctly implements the precedence rules (definition skip > definition require > CLI skip flags). No security vulnerabilities, data loss scenarios, or critical logic errors found.
Summary
Adds pre-flight checks that run automatically before mutating model methods (create/update/delete/action), catching invalid inputs early with clear error messages instead of letting them fail as raw API errors.
Closes #619
Core Implementation
CheckDefinitioninterface withdescription,labels,appliesTo, andexecute(context) => CheckResultCheckResultwithpass: booleanand optionalerrors: string[]isMutatingKind()helper for method kind classificationExtension Checks
ModelRegistry.extend()accepts optional thirdchecksparametermethods: array ofRecord<string, CheckDefinition>Definition-Level Check Selection
CheckSelectiontype:{ require?: string[]; skip?: string[] }on definitionsskipalways wins (even overrequire)requirechecks are immune to CLI skip flags (--skip-checks,--skip-check,--skip-check-label)CLI Flags
model method run:--skip-checks,--skip-check <name>,--skip-check-label <label>model validate:--label <label>,--method <method>for check filteringValidation Service
model validateoutputTest plan
deno check,deno lint,deno fmtpasscommand/shellmodel extension checks:--skip-check-label)--skip-check)--skip-checks)model validatewith--labeland--methodfiltering🤖 Generated with Claude Code