Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/skills/swamp-extension-model/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const model = {
| `files` | No | File output specs (binary/text with content type) |
| `inputsSchema` | No | Zod schema for runtime inputs |
| `methods` | Yes | Object of method definitions with `arguments` Zod |
| `checks` | No | Pre-flight checks run before mutating methods |

## Resources & Files

Expand Down Expand Up @@ -333,6 +334,14 @@ model.
(droplets, EC2 instances). Not needed when the API is naturally idempotent
(tags, S3 buckets) or you intentionally want multiple instances.

## Pre-flight Checks

Checks run automatically before mutating methods (`create`, `update`, `delete`,
`action`). Define them on `checks` in the model export — see the Quick Start
example above. For the full `CheckDefinition` interface, labels conventions,
`appliesTo` scoping, and extension checks, see
[references/checks.md](references/checks.md).

## Extending Existing Model Types

Add new methods to existing model types without changing their schema. Use
Expand All @@ -359,11 +368,16 @@ export const extension = {
};
```

Extensions can also add pre-flight checks — see
[references/checks.md](references/checks.md#extension-checks) for the format.

**Extension rules:**

- Extensions **cannot** change the target model's Zod schema
- Extensions **only** add new methods — no overriding existing methods
- `methods` is always an array of `Record<string, MethodDef>` objects
- `checks` is always an array of `Record<string, CheckDefinition>` objects
- Check and method names must not conflict with existing ones on the target type

## Model Discovery

Expand Down Expand Up @@ -471,6 +485,9 @@ swamp model type describe @myorg/my-model --json # Check schema

- **API Reference**: See [references/api.md](references/api.md) for detailed
`writeResource`, `createFileWriter`, `DataWriter`, and logging API docs
- **Pre-flight Checks**: See [references/checks.md](references/checks.md) for
`CheckDefinition` interface, `CheckResult`, labels, scoping, and extension
checks
- **Examples**: See [references/examples.md](references/examples.md) for
complete model examples (CRUD lifecycle, data chaining, extensions, etc.)
- **Scenarios**: See [references/scenarios.md](references/scenarios.md) for
Expand Down
106 changes: 106 additions & 0 deletions .claude/skills/swamp-extension-model/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,112 @@ step to catch exceptions and allow continued execution of subsequent steps.

---

## CheckDefinition API

Pre-flight checks are defined in the model's `checks` field and run
automatically before mutating methods (`create`, `update`, `delete`, `action`).

### execute Signature

```typescript
execute: async (context: MethodContext): Promise<CheckResult>
```

### MethodContext Fields Available in Checks

| Field | Description |
| ------------------------ | ---------------------------------------------- |
| `context.globalArgs` | Validated global arguments from the definition |
| `context.definition` | `{ id, name, version, tags }` |
| `context.methodName` | Name of the method being invoked |
| `context.repoDir` | Repository root path |
| `context.logger` | LogTape Logger for diagnostic output |
| `context.dataRepository` | For reading previously stored data |
| `context.modelType` | The model type string |
| `context.modelId` | The model instance ID |

**Important:** `context.writeResource` and `context.createFileWriter` are **NOT
available** in check execute functions. Checks must not produce data output —
they only inspect state and return a pass/fail result.

### CheckResult

```typescript
interface CheckResult {
pass: boolean;
errors?: string[]; // required when pass is false; human-readable reasons
}
```

### Three Common Patterns

**1. Value/policy validation** — inspect `context.globalArgs` directly:

```typescript
execute: async (context) => {
if (context.globalArgs.budget < 0) {
return { pass: false, errors: ["budget must be non-negative"] };
}
return { pass: true };
},
```

**2. Cross-model validation** — read other model's stored data via
`context.dataRepository`:

```typescript
execute: async (context) => {
const content = await context.dataRepository.getContent(
"aws/vpc",
context.globalArgs.vpcId,
"state",
);
if (!content) {
return { pass: false, errors: [`VPC ${context.globalArgs.vpcId} has no stored state`] };
}
return { pass: true };
},
```

**3. Live API checks** — call an external API to verify conditions:

```typescript
execute: async (context) => {
const res = await fetch(`https://api.example.com/quotas/${context.globalArgs.region}`);
const quota = await res.json();
if (quota.remaining < 1) {
return { pass: false, errors: [`No quota remaining in region ${context.globalArgs.region}`] };
}
return { pass: true };
},
```

Label live checks with `labels: ["live"]` so users can skip them in offline
environments using `--skip-check-label live`.

### Extension Checks via modelRegistry.extend()

The `modelRegistry.extend()` method accepts an optional third parameter
`checks?: Record<string, CheckDefinition>` to add checks to an existing model
type:

```typescript
modelRegistry.extend("aws/ec2/vpc", {}, {
"my-custom-check": {
description: "Custom policy check added by extension",
labels: ["policy"],
execute: async (context) => {
return { pass: true };
},
},
});
```

Check names must be unique -- conflicts with existing checks on the target model
throw an error at registration time.

---

## Logging API

Model methods have access to a pre-configured LogTape logger via
Expand Down
107 changes: 107 additions & 0 deletions .claude/skills/swamp-extension-model/references/checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Pre-flight Checks Reference

Pre-flight checks run automatically before any mutating method (`create`,
`update`, `delete`, `action`). They validate conditions before execution —
avoiding half-completed operations.

## CheckDefinition Interface

```typescript
checks: {
"check-name": {
description: "Human-readable description of what is validated",
labels: ["policy", "live"], // optional categorization tags
appliesTo: ["create", "update"], // optional: limit to specific methods
execute: async (context) => {
// context has: globalArgs, definition, methodName, repoDir, logger,
// dataRepository, modelType, modelId
// NOTE: writeResource and createFileWriter are NOT available in checks
return { pass: true };
// or: return { pass: false, errors: ["Reason check failed"] };
},
},
},
```

## CheckResult

```typescript
interface CheckResult {
pass: boolean;
errors?: string[]; // human-readable failure reasons when pass is false
}
```

## Example: Value/Policy Validation

```typescript
checks: {
"valid-region": {
description: "Ensure the target region is an allowed region",
labels: ["policy"],
execute: async (context) => {
const allowed = ["us-east-1", "us-west-2", "eu-west-1"];
const region = context.globalArgs.region;
if (!allowed.includes(region)) {
return {
pass: false,
errors: [`Region "${region}" is not in the allowed list: ${allowed.join(", ")}`],
};
}
return { pass: true };
},
},
},
```

## Labels Convention

Use labels to categorize checks for selective skipping:

- `policy` — business rules and constraints
- `live` — checks that make live API calls
- `dependency` — cross-model dependency validation

## appliesTo Scoping

If `appliesTo` is omitted, the check runs before all mutating methods. To scope
a check to specific methods:

```typescript
appliesTo: ["create"], // only on create
appliesTo: ["create", "update"], // on create and update, not delete
```

## Skipping Checks

```bash
swamp model method run my-model create --skip-checks # skip all
swamp model method run my-model create --skip-check valid-region # skip by name
swamp model method run my-model create --skip-check-label live # skip by label
```

## Extension Checks

Extensions can add checks to existing model types. The `checks` field is an
array of `Record<string, CheckDefinition>` objects, following the same
array-of-records pattern as `methods`:

```typescript
export const extension = {
type: "aws/ec2/vpc",
methods: [],
checks: [{
"no-cidr-overlap": {
description: "Ensure CIDR does not overlap with existing VPCs",
labels: ["policy"],
execute: async (context) => {
// validation logic
return { pass: true };
},
},
}],
};
```

Check names must not conflict with checks already defined on the target model
type — conflicts throw an error at registration time.
Loading
Loading