> ## Documentation Index
> Fetch the complete documentation index at: https://s2.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Declarative Spec

> Define S2 basins and streams in a JSON spec and apply it with the CLI for declarative, version-controlled infrastructure.

Define your S2 infrastructure in a JSON spec file and apply it declaratively using the CLI. Resources that already exist are reconfigured to match the spec, and only the fields present in the spec are updated.

```bash theme={null}
s2 apply -f spec.json
```

## Spec file format

The spec is a JSON file with a top-level `basins` array. Each basin entry has a `name`, an optional `config`, and an optional `streams` array.

```json theme={null}
{
  "$schema": "https://raw.githubusercontent.com/s2-streamstore/s2/main/cli/schema.json",
  "basins": [
    {
      "name": "my-basin",
      "config": {
        "stream_cipher": "aegis-256",
        "create_stream_on_append": true,
        "default_stream_config": {
          "storage_class": "express",
          "retention_policy": "7days"
        }
      },
      "streams": [
        {
          "name": "events",
          "config": {
            "storage_class": "standard",
            "retention_policy": "infinite"
          }
        }
      ]
    }
  ]
}
```

### IDE setup

Add the `$schema` key to your spec file to point to the published schema hosted on GitHub to enable inline validation and autocomplete in your editor:

```json theme={null}
{
  "$schema": "https://raw.githubusercontent.com/s2-streamstore/s2/main/cli/schema.json",
  "basins": []
}
```

Or generate a local copy and reference it by path:

```bash theme={null}
s2 apply --schema > schema.json
```

```json theme={null}
{
  "$schema": "./schema.json",
  "basins": []
}
```

**VS Code**: configure schema associations in `.vscode/settings.json` so `$schema` is not needed in every file:

```json theme={null}
{
  "json.schemas": [
    {
      "fileMatch": ["*.s2spec.json", "s2-spec.json"],
      "url": "https://raw.githubusercontent.com/s2-streamstore/s2/main/cli/schema.json"
    }
  ]
}
```

You can use any glob pattern in `fileMatch` to match your spec files by naming convention.

**IntelliJ / JetBrains IDEs**: add a schema mapping in the IDE settings:

1. Open **Settings** (`Ctrl+Alt+S` / `⌘,`) → **Languages & Frameworks** → **Schemas and DTDs** → **JSON Schema Mappings**
2. Click **+** to add a new mapping
3. Give the mapping a name (e.g. `S2 Spec`)
4. Set the schema to `https://raw.githubusercontent.com/s2-streamstore/s2/main/cli/schema.json` (or a local path)
5. Set the **Schema version** to **JSON Schema version 7**
6. Add file patterns to associate (e.g. `*.s2spec.json`)

### Basin config fields

| Field                     | Type                             | Description                                                            |
| ------------------------- | -------------------------------- | ---------------------------------------------------------------------- |
| `default_stream_config`   | object                           | Default stream configuration applied to newly created streams          |
| `stream_cipher`           | `"aegis-256"` \| `"aes-256-gcm"` | Cipher to apply to streams created after this basin config is in place |
| `create_stream_on_append` | boolean                          | Auto-create a stream on first append using the default config          |
| `create_stream_on_read`   | boolean                          | Auto-create a stream on first read using the default config            |

### Stream config fields

| Field              | Type                             | Description                                               |
| ------------------ | -------------------------------- | --------------------------------------------------------- |
| `storage_class`    | `"express"` \| `"standard"`      | Storage class for recent writes                           |
| `retention_policy` | `"infinite"` \| humantime string | How long records are retained (e.g. `"7days"`, `"1week"`) |
| `timestamping`     | object                           | Timestamping behavior (see below)                         |
| `delete_on_empty`  | object                           | Delete the stream automatically when empty                |

#### Timestamping

```json theme={null}
{
  "mode": "client-prefer",
  "uncapped": false
}
```

| Field      | Values                                                 | Description                                               |
| ---------- | ------------------------------------------------------ | --------------------------------------------------------- |
| `mode`     | `"client-prefer"` \| `"client-require"` \| `"arrival"` | How record timestamps are resolved                        |
| `uncapped` | boolean                                                | Allow client timestamps to exceed the server arrival time |

#### Delete-on-empty

```json theme={null}
{
  "min_age": "1day"
}
```

Set `min_age` to a humantime duration (e.g. `"1day"`, `"2h 30m"`) to enable automatic deletion of empty streams once they reach that age. The default of `"0s"` disables delete-on-empty.

## Dry run

Preview what would change without making any mutations:

```bash theme={null}
s2 apply -f spec.json --dry-run
```

Output uses the following legend:

| Symbol | Meaning                                        |
| ------ | ---------------------------------------------- |
| `+`    | Will be created                                |
| `~`    | Will be reconfigured (shows field-level diffs) |
| `=`    | Already matches spec, no change                |

Example output:

```
+ basin new-basin
    storage_class: express
  + stream new-basin/events
    retention_policy: 7days
= basin existing-basin
  ~ stream existing-basin/logs
      retention_policy: 7days → 30days
```

For the full list of CLI flags, see [`s2 apply`](/cli/apply).
