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
11 changes: 7 additions & 4 deletions docs/docs/design/type-bounds.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Bounds on types

Status: **accepted 2026-11-02; steps 1–6 shipped, 7–12 in progress** ·
Status: **accepted 2026-11-02; steps 1–10 shipped, except step 10's
worst-case sizes against configured limits; 11–12 in progress** ·
[core#98](https://github.com/ComlineProject/core/issues/98) · Decided through
[the proposals](../proposals/type-bounds/index.md):
[Proposal D](../proposals/type-bounds/max-only.md), with
Expand Down Expand Up @@ -112,8 +113,10 @@ receivers. Adopting a maximum is breaking too, accepted before 1.0.

### Validators

- **Enforced by default.** `validate_on_decode` defaults to checking; turning it
off is an explicit give-up with a reason.
- **Enforced by default.** `validate_on_decode` defaults to `collect`; turning it
off is an explicit give-up with a reason, `validate_on_decode = { off = "why" }`
in `comline.toml`. A bare `"off"` is refused, and the give-up shows in
`comline generate` and `comline inspect`.
- **std gains** `NonEmpty`, `MinChars`, `MinItems` and `IntRange`.
`StringBounds.max_chars` defaults to `u32::MAX`, then is deprecated.
- **Generators build the std minimum validators into the decoder,** at a
Expand Down Expand Up @@ -172,6 +175,6 @@ runtime core primitive is used only where measurement shows it's faster.
| 7 | runtime | conformance vectors for bounds per format, the depth limit, and bounds in the compatibility facts |
| 8 | comline-rust | checks while decoding and encoding, alias newtypes, validators on by default |
| 9 | comline-typescript | checks while decoding and encoding, branded aliases; validators with core#96's step 4d |
| 10 | cli | `validate_on_decode` default, worst-case sizes against configured limits, migration |
| 10 | cli | `validate_on_decode` default, worst-case sizes against configured limits, migration. **Shipped:** `validate_on_decode` defaults to `collect`, and off is `{ off = "reason" }`; the migration (the build lists each unbounded position, shows the `settings` block to add, and offers to write it from the author's input); the pin bump. **Left:** checking worst-case sizes against configured limits |
| 11 | language-server | hover sizes and cost per target, the nested-bounds lint |
| 12 | docs | the guide: bounds, `char`, and the migration |
2 changes: 1 addition & 1 deletion docs/docs/design/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ left to right, parenthesising each comparison.
|---|---|
| 4a | **Done (core#108).** Freeze the `validate` condition as a structured expression tree (keep the text + `references` the checks use). |
| 4b | **Done for Rust.** `rust` generator emits `validate()` (collect-all) and `validate_first()` (fail-fast); stand up the `comline_rt` support crate with `ValidationError` / `ValidationErrors`; implement the `value.*` accessor set. |
| 4c | **Done for Rust.** `comline.toml` `validate_on_decode` knob (`off` / `collect` / `first`) — `#[serde(try_from)]` shim in the `rust` target; default `off`. [Sketch below](#auto-validation-on-decode-4c). |
| 4c | **Done for Rust.** `comline.toml` `validate_on_decode` knob (`off` / `collect` / `first`) — `#[serde(try_from)]` shim in the `rust` target; default `off` then; `collect` since bounds step 10 ([core#98](https://github.com/ComlineProject/core/issues/98)), with off given up as `{ off = "reason" }`. [Sketch below](#auto-validation-on-decode-4c). |
| 4d | The other generators (python / typescript / luau) once they exist; imported / cross-schema validators (needs the imported schema's IR at generate time, like multi-version `generate`). |

### Auto-validation on decode (4c)
Expand Down
10 changes: 6 additions & 4 deletions docs/docs/guide/idl/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,12 @@ Each `ValidationError` carries `field`, the path to the failing value
filled in (`friends[1] must be at least 2 characters`). An absent `optional`
field skips its checks.

Nothing calls `validate()` for you unless you ask:
[`validate_on_decode`](../../reference/congregation/comline-toml.md#validate_on_decode)
in `comline.toml` makes decoding run the checks, and makes a provider refuse a
call whose arguments fail them.
Decoding runs the checks for you by default (`validate_on_decode = "collect"`
in `comline.toml`; `"first"` stops at the first failure), and a provider
refuses a call whose arguments fail them. Turning that off is a give-up with
its reason, `validate_on_decode = { off = "why" }`; then nothing runs until you
call `validate()`. See
[`validate_on_decode`](../../reference/congregation/comline-toml.md#validate_on_decode).

Properties are filled in when the code is generated: the argument you wrote,
else the property's default, with constants resolved. Nothing about the
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/guide/ir/cas.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,10 @@ A ref that was already resolved by picking one side does not reconcile on its
own: the build sees one head, and the other side's commits are simply not on
the line. Its changes are still built from the merged working tree. To record
the dropped head as well, core's `cas::reconcile::mark_diverged` puts it back
beside the current one, and the next build reconciles the two. The CLI does
not expose this yet.
beside the current one, and the next build reconciles the two. From the CLI,
`comline build --reconcile <hash>` does both, given the dropped head's full
hash. Either way, the build says it reconciled: which head the line continued
from, and which one it recorded as the second parent.

## Compared with Git

Expand Down
8 changes: 5 additions & 3 deletions docs/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ parse → resolve imports → validate → (freeze into .comline/) → (generate
| `comline accept <name>[::<protocol>] [--yes]` | Accept what a dependency's [`@unverified`](../guide/idl/protocol.md#giving-up-a-guarantee) protocols give up: shows each one's given-up guarantees and reason, then writes `accept_unverified` into its `config.idp` entry, leaving the rest as written. Only protocols not already accepted for exactly their current set are asked about. On a terminal you're asked (default no); without one it refuses (exit `2`) unless `--yes` (`-y`). |
| `comline check` | Parse, resolve and validate. No `.comline/` write (beyond fetching git dependencies), no version bump — safe for editors, hooks, CI. |
| `comline inspect [--json]` | Show each protocol's format, framing and handshake, with their parameters and where each came from (`@framing`/`@handshake`, `config.idp`, or core's default), then every guarantee, held or given up, with the cause, the `@unverified` reason and whether it's accepted. No `.comline/` write. `--json` prints the same as JSON. See [comline inspect](tools/inspect.md). |
| `comline build [--watch]` | Compile, validate, and freeze a build into `.comline/` under a pre-release version (`0.3.0-dev.2`); print the changelog, the bump, and the next release. Before freezing, proves every declared `code_generation.languages` target against this binary's generators ([Language versions](../guide/codegen/generating-code.md#language-versions)). Releasing is `comline release`. |
| `comline build [--watch] [--reconcile <hash>]` | Compile, validate, and freeze a build into `.comline/` under a pre-release version (`0.3.0-dev.2`); print the changelog, the bump, and the next release. `--reconcile <hash>` puts a head dropped in a Git merge back beside the current one, so the build [reconciles the two](../guide/ir/cas.md); the output says when a build reconciled. A project with [unbounded strings or lists](../guide/idl/types.md#every-string-and-list-has-a-maximum) fails with each position listed and the `settings` block to add; on a terminal, the build offers to write it with maxima you type in. Before freezing, proves every declared `code_generation.languages` target against this binary's generators ([Language versions](../guide/codegen/generating-code.md#language-versions)). Releasing is `comline release`. |
| `comline generate [--target <lang>] [--out <dir>] [--layout <tpl>] [--mode <m>] [--external-std] [--no-external-std] [--validate-on-decode <when>] [--dry-run] [--list] [--watch]` | Validate (no freeze), then write generated code. Location/layout from [`comline.toml`](congregation/comline-toml.md); targets from there or `config.idp`. A target can use an [external generator](../guide/extending/generators.md) (`generator = "<path>"`) and map [encodings](../guide/extending/encodings.md#5-map-it-to-a-type). |
| `comline release [1.0] [--raise <level> --reason <text>] [--yes]` | Build, then release: create the version from the highest bump since the last release ([builds and releases](versioning.md#builds-and-releases)). `--raise patch\|minor\|major` goes higher, with a `--reason` recorded with the release; it never lowers. `1.0` makes a `0.x` package stable after showing what that commits to (its protocols, and what's worth settling first). Raising to major and `1.0` ask on a terminal; without one they refuse (exit `2`) unless `--yes` (`-y`). |
| `comline diff <old> [<new>]` | Show schema changes between two built versions. Each argument is a version (`0.2.0`), a commit hash (4+ chars), or `HEAD` (`<new>` default). |
Expand All @@ -54,10 +54,12 @@ reference std's own package — `comline_std` for rust, `@comline/std` for
typescript — instead of inlining it, and `--no-external-std` inlines it;
either overrides [`comline.toml`](congregation/comline-toml.md)'s
`[generate] external_std`, which itself defaults to on in `lib` mode and off
in `code` mode. `--validate-on-decode off|collect|first` overrides
in `code` mode. `--validate-on-decode collect|first` overrides
[`[generate] validate_on_decode`](congregation/comline-toml.md#validate_on_decode),
whether decoding runs the schema's `@validators` (also
`COMLINE_GENERATE_VALIDATE_ON_DECODE`).
`COMLINE_GENERATE_VALIDATE_ON_DECODE`). The default is `collect`; turning it
off is a give-up written in `comline.toml` with its reason
(`{ off = "why" }`), and `generate` notes it per target.
Each target's files are drawn as a tree. `--list` prints them one per line
instead, with the schema each came from — and, when several package versions
are generated at once, the version each line belongs to. `--dry-run` writes
Expand Down
33 changes: 24 additions & 9 deletions docs/docs/reference/congregation/comline-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Defaults for every target.
| `mode` | `"code"` \| `"lib"` \| `"dylib"` | `"code"` | Emit form. `code` and `lib` are implemented for `rust` and `typescript`; `dylib` is planned — see [Library generation](../../guide/codegen/library-generation.md). |
| `package_versions` | `"latest"` \| `"all"` \| list | `"latest"` | Which package versions to emit — see [below](#package_versions). |
| `external_std` | bool | on in `lib`, off in `code` | Reference `std` as its own package instead of inlining it — see [below](#external_std). |
| `validate_on_decode` | `"off"` \| `"collect"` \| `"first"` | `"off"` | Whether decoding a value runs its [`@validators`](../../guide/idl/validator.md) — see [below](#validate_on_decode). |
| `validate_on_decode` | `"collect"` \| `"first"` \| `{ off = "why" }` | `"collect"` | Whether decoding a value runs its [`@validators`](../../guide/idl/validator.md); off is a give-up with its reason — see [below](#validate_on_decode). |
| `async` | bool | `false` | Generate protocols on a connection for an async driver — see [below](#async). |
| `encodings` | table | — | Which type implements each encoding (format, framing, handshake) in the generated code — see [below](#encodings). |
| `only` | list of strings | — | Generate just these languages, out of the ones `config.idp` declares — see [below](#only). |
Expand Down Expand Up @@ -147,18 +147,33 @@ whether **decoding** calls them too:

| Value | Decoding a value that fails its checks |
|---|---|
| `"off"` | succeeds; nothing runs until you call `validate()` |
| `"collect"` | fails, with every failed check |
| `"collect"` (the default) | fails, with every failed check |
| `"first"` | fails, with the first failed check |
| `{ off = "why" }` | succeeds; nothing runs until you call `validate()` |

It's yours to choose rather than the schema's: it changes what decoding costs,
and a form that wants every problem at once and a hot path that wants to stop
early want different things from the same type. With it on, a provider also
refuses a call whose arguments fail their checks, the same way it refuses
arguments that don't decode.
Validators are enforced by default: a schema's checks hold wherever its values
are decoded. Choosing between `collect` and `first` is yours rather than the
schema's: a form that wants every problem at once and a hot path that wants to
stop early want different things from the same type. A provider also refuses a
call whose arguments fail their checks, the same way it refuses arguments that
don't decode.

Not running them is a **give-up**, like [`@unverified`](../../guide/idl/protocol.md),
and it carries its reason:

```toml
[generate]
validate_on_decode = { off = "the gateway validates every request first" }
```

A bare `"off"` is refused, with that fix. `comline generate` notes each target
that gives it up, with the reason, and `comline inspect` lists it after the
protocols (`--json`: `validate_on_decode_given_up`).

Today only the `rust` generator runs validators; `typescript` ignores this.
`--validate-on-decode` and `COMLINE_GENERATE_VALIDATE_ON_DECODE` override it.
`--validate-on-decode collect|first` and `COMLINE_GENERATE_VALIDATE_ON_DECODE`
override it for one run; turning it off is written in `comline.toml`, where
its reason is kept.

### `async`

Expand Down
66 changes: 50 additions & 16 deletions docs/docs/schemas/comline-toml/0.x.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"type": "string"
},
"default": {},
"description": "Where each dependency's documentation lives, by the name the package\ngives the dependency (or `std`): `links = { std = \"https://...\" }`. A\ntype from a dependency listed here links into its docs; one that\nisn't is plain text.",
"description": "Where each dependency's documentation lives, by the name the package\ngives the dependency (or `std`): `links = { std = \"https://...\" }`. A\ntype from a dependency listed here links into its docs; one that\nisn't links to the pages renderers write for it under `deps/`.",
"type": "object"
},
"out": {
Expand Down Expand Up @@ -215,14 +215,31 @@
"type": "array"
},
"validate_on_decode": {
"default": "off",
"description": "Whether decoding a value runs its `@validators` checks: `off` (the\ndefault), `collect` (fail with every failure) or `first` (fail with\nthe first). Shared by every target unless overridden.",
"enum": [
"off",
"collect",
"first"
],
"type": "string"
"default": "collect",
"description": "Whether decoding a value runs its `@validators` checks: `collect`\n(the default: fail with every failure) or `first` (fail with the\nfirst). Not running them is a give-up that carries its reason:\n`{ off = \"why\" }`. Shared by every target unless overridden.",
"oneOf": [
{
"enum": [
"collect",
"first"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Decoding doesn't run the checks: a give-up, with its reason.",
"properties": {
"off": {
"minLength": 1,
"type": "string"
}
},
"required": [
"off"
],
"type": "object"
}
]
}
},
"type": "object"
Expand Down Expand Up @@ -294,13 +311,30 @@
"type": "integer"
},
"validate_on_decode": {
"description": "Whether this target's decoding runs `@validators` checks: `off`,\n`collect` or `first`. Overrides `[generate] validate_on_decode`.",
"enum": [
"off",
"collect",
"first"
],
"type": "string"
"description": "Whether this target's decoding runs `@validators` checks: `collect`,\n`first`, or `{ off = \"why\" }`. Overrides `[generate]\nvalidate_on_decode`.",
"oneOf": [
{
"enum": [
"collect",
"first"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Decoding doesn't run the checks: a give-up, with its reason.",
"properties": {
"off": {
"minLength": 1,
"type": "string"
}
},
"required": [
"off"
],
"type": "object"
}
]
}
},
"type": "object"
Expand Down
12 changes: 10 additions & 2 deletions docs/docs/schemas/doc-model/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@
"type": "object"
},
{
"description": "An annotation that changes what travels on the wire (`@framing`).",
"description": "An annotation that changes what travels on the wire (`@framing`,\n`@handshake`) or what a decoder accepts (`@max_depth`, set on the\ntype or reaching it from a `bounds.depth.max` default).",
"properties": {
"key": {
"type": "string"
Expand Down Expand Up @@ -1728,7 +1728,7 @@
"type": "object"
},
{
"description": "`T[]` (`length: None`) or `T[N]`.",
"description": "`T[]`, `T[N]` (`length`) or `T[max N]` (`max`), at most one of the\ntwo. An array of `char` is a string: `char[max 64]` holds at most 64\ncharacters. `max` is absent when there is none.",
"properties": {
"element": {
"$ref": "#/definitions/Type"
Expand All @@ -1744,6 +1744,14 @@
"integer",
"null"
]
},
"max": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"required": [
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/std/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ Variants:

An HTTP request: its method and target URI.

| Field | Type |
|---|---|
| `method` | [`HttpMethod`](#httpmethod) |
| `uri` | `string` |
| Field | Type | Description |
|---|---|---|
| `method` | [`HttpMethod`](#httpmethod) | |
| `uri` | `char[max 8192]` | At most 8192 characters, as most servers allow. |

<a id="response"></a>

Expand Down
Loading
Loading