Real‑world examples
These are intentionally practical: how wildcard keys (*:), unions, and constraints map to popular configuration ecosystems.
Docker Compose
Wildcard service names and structured per-service constraints.
Typical YAML
services:
web:
image: nginx:latest
ports:
- "80:80"
environment:
- NODE_ENV=production
volumes:
- ./app:/app
CSL schema
config DockerCompose {
services: {
*: { // Wildcard service names
image?: string @regex(".+:.+");
build?: {
context: string;
dockerfile?: string;
};
ports?: string[] @regex("^\\d+:\\d+$");
environment?: {
*: string | number | boolean;
};
volumes?: string[];
networks?: string[];
constraints {
conflicts build with image;
validate exists(ports) ? environment.NODE_ENV != "test" : true;
};
};
};
volumes?: { *: {}; };
networks?: {
*: {
driver?: "bridge" | "overlay";
external?: boolean;
};
};
}
Features used: wildcard keys (
*:), unions, regex annotations, and a constraints block.
GitHub Actions
Validate mutually exclusive fields (uses vs run) inside steps.
Typical workflow YAML
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
CSL schema
config GitHubActions {
name?: string;
on: string | string[] | {
push?: { branches?: string[] };
pull_request?: { paths?: string[] };
};
env?: { *: string | number; };
jobs: {
*: { // Job names
runs-on: "ubuntu-latest" | "macos-latest" | "windows-latest";
needs?: string[];
steps: {
name?: string;
uses?: string @regex("^actions/");
run?: string;
with?: { *: string | number | boolean; };
constraints {
validate exists(uses) || exists(run);
conflicts uses with run;
validate uses ? with : true;
};
}[];
};
};
}
The key trick is scoping constraints inside the
steps object so they apply per-step, consistently.
AWS CloudFormation
Use any{} for flexible resource properties while keeping the outer structure strict.
Sample template excerpt
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-app-bucket
AccessControl: Private
CSL schema
config CloudFormation {
Resources: {
*: { // Resource names
Type: string @start_with("AWS::");
Properties: any{};
DependsOn?: string | string[];
Metadata?: any{};
constraints {
validate Type == "AWS::S3::Bucket" ?
Properties.BucketName @regex("^[a-z0-9-]+$") :
true;
};
};
};
Parameters?: {
*: {
Type: "String" | "Number" | "List";
Default?: string | number;
AllowedValues?: any[];
};
};
Outputs?: {
*: {
Value: string;
Export?: { Name: string };
};
};
}
This pattern keeps CloudFormation’s “huge surface area” manageable while still catching obvious mistakes early.
Have another example?
If you’ve written a schema for a real config format, we’d love to see it.