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
86 changes: 70 additions & 16 deletions docs/docs/proposals/extensibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,24 @@ encodings = {
`@framing`.

Core can't run another language's code, so it trusts the profile the way it
trusts that dependency's schemas: as declared, and proven in the author's CI.
trusts that dependency's schemas: as declared. Whether the profile is proven
depends on the vectors behind it (see [Test vectors and trust](#test-vectors-and-trust)).
An encoding named with no profile anywhere is compared under the strictest
profile, with a warning.

### Test vectors and trust

Decided 2026-10-20 ([core#120](https://github.com/ComlineProject/core/issues/120)):

| Kind | Requirement | Result |
|---|---|---|
| **Official** | Vectors for every fact in its profile, implementations for every official target, conformance passing in CI, and a spec page: everything Comline requires | Fully trusted |
| **Third-party, with vectors** | Vectors shipped by its package, and a check (say `comline conformance`) that runs an implementation against them | Conformance proven; nothing given up |
| **Third-party, without vectors** | None | Encoding conformance is [given up](#giving-up-a-guarantee): `@unverified` with a reason, and the consumer's acceptance |

Nothing forces a third-party author to write vectors, but skipping them is
visible to every consumer.

### How generated code reaches one

A generator doesn't need to understand a framing to wire it. It needs to know
Expand All @@ -102,9 +116,14 @@ language = "rust"
encodings = { "com.acme.frame@1" = "acme_frame::AcmeFraming" }
```

- **The encoding's package can suggest a default mapping** per language (in
Rust `acme_frame::AcmeFraming`, in TypeScript `@acme/frame`), so a consumer
writes nothing unless it wants something else (decided 2026-10-20).
Shipping the implementation itself, so generated `lib` output depends on it
automatically, waits for the third-party half of dependency publishing.
- The generator emits the mapped type where it would emit `JsonRpcFraming`.
- An encoding that's declared but not mapped for a target is a **generation
error**, never a fallback.
- An encoding that's declared but neither mapped nor suggested for a target is
a **generation error**, never a fallback.
- The per-package format trait
([generation#67](https://github.com/ComlineProject/generation/issues/67)) is
implemented for each mapped name, so it can't be sealed. A sealed trait would
Expand Down Expand Up @@ -338,6 +357,52 @@ protocol Billing {
guarantee is major, since peers relied on it, and so is the set growing
("`Billing` now also gives up schema agreement"). Restoring one is minor.

### At the package level

Decided 2026-10-20 ([core#121](https://github.com/ComlineProject/core/issues/121)).
Some cases aren't on a protocol:

- **An encoding without vectors:** the reason goes where the encoding is
declared in `config.idp`:

```
framings = {
"legacy" = {
encoding = "com.acme.legacy@1"
unverified = "no vectors yet; tested by hand against the 2009 gateway"
}
}
```

- **Lowering a bump:** a decision about one release, made with
`comline release --lower … --reason "…"` and stored on the release record
([Builds and releases](schema-compatibility.md#builds-and-releases)), not in
`config.idp`.
- **A consumer's acceptance,** under the dependency in its own `config.idp`:

```
dependencies = {
billing = {
path = "../billing"
accept_unverified = {
Billing = ["schema_agreement"]
}
}
}
```

- The list is the set accepted, a snapshot, so the build stops again if
the set grows. It's the one place guarantee names are written; on the
producer's side core works them out.
- **Nobody types it by hand:** the build error offers a quick fix, and
`comline accept billing::Billing` writes the entry, as ids are written
into the source on build.
- **Diagnostics:** given-up guarantees get their own code family
(`unverified::schema-agreement`, …) under `diagnostics.<code>.level`, so an
organisation can raise the informational warnings to errors or quiet them.
The "not accepted" error can't be lowered: if it could, accepting would
mean nothing.

## Transports and dispatchers

Neither changes what's on the wire, so neither needs a name, a profile or a
Expand All @@ -363,22 +428,11 @@ mapping already covers that.

## Open questions

- **Trust.** Is "declared by a dependency" enough, or should the dependency
have to ship vectors as well? Could a tool then check a third-party
implementation against them?
- **The mapping's default.** Should an encoding's package be able to suggest the
type that implements it in each language, so the consumer's `comline.toml`
only overrides it?
- **Shipping implementations.** Could an encoding package carry its crate or npm
package, so `lib`-mode output depends on it automatically?
package, so `lib`-mode output depends on it automatically? Waits for the
third-party half of dependency publishing.
- **Transports in the handshake.** Should a transport ever be named, as a
capability for instance, or stay invisible?
- **The marker** (its name, and whether it names the guarantee) is weighed in
[Marking a given-up guarantee](guarantee-marker/index.md).
- **Package-level cases:** how the marker for an unproven format or a
version-rule override, and the consumer's acceptance, read in `config.idp`.
- **Diagnostics.** Should given-up guarantees be their own diagnostics code
family, so an organisation can raise or lower its warnings?

## What it touches

Expand Down
11 changes: 11 additions & 0 deletions docs/docs/proposals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ How phases are written. Two competing proposals:
declaration lists each phase in one place. *rejected: a new construct and a
second table.*

#### [Tagged unions](tagged-unions/index.md)

Whether "one of several shapes, with a tag" is a feature of `enum` or of
`union`. Three competing proposals, *under discussion*:

- [Proposal A — a `union` declaration](tagged-unions/union-declaration.md):
members written like struct fields, tagged with `@id`. *leaning.*
- [Proposal B — enum variants that carry data](tagged-unions/enum-variants.md),
as in Rust and Swift.
- [Proposal C — both](tagged-unions/both.md).

### Tooling

- [Automatic documentation](auto-documentation.md) — an optional tool that
Expand Down
37 changes: 21 additions & 16 deletions docs/docs/proposals/packet-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ and already used in `runtime-std-extra`'s `reverse_proxy.ids`:
- **`@provider`** (`Server`, `Client` or `Multiple`) names the side that
**implements** a protocol. That's the runtime's existing sense of
"provider": the provider trait, provider faults.
- **`@consumer`** names the side that calls it.
- **`@consumer`** names the side that calls it, and nothing more: it's the
counterpart of `@provider` (decided 2026-10-20). When `@provider` is set,
`@consumer` follows from it. Writing both is allowed, but a mismatch is an
error. The other reading of the 2024 file, *how many* peers provide or
consume (a fleet behind a reverse proxy), is dropped; if that need returns,
it belongs with [connections](connections.md).
- **Server-to-client packets** are a protocol with `@provider = Client`. The
client gets the dispatcher and the provider trait for it, and the server
gets the sender.
Expand Down Expand Up @@ -321,7 +326,8 @@ with sub-opcodes), are **one packet whose payload is a tagged union** of
structs. The second level of dispatch, hand-written in most implementations,
is generated. Unions travel untagged today, so this needs tagged unions with
explicit tag values
([core#126](https://github.com/ComlineProject/core/issues/126)), which serve
([core#126](https://github.com/ComlineProject/core/issues/126); how they're
written is weighed in [Tagged unions](tagged-unions/index.md)), which serve
more than packets.

### The mechanism, plus an official framing
Expand Down Expand Up @@ -404,10 +410,17 @@ Also part of it:
parameters along with the name. A legacy handshake can't check them; an
`@unverified` protocol vouches for them too.

Open: whether an instance name may shadow an encoding name
(`"jsonrpc@1" = {…}`), and whether instances can be shared across packages
through a dependency, so two packages speaking one legacy system reuse one
definition.
Decided 2026-10-20 ([core#117](https://github.com/ComlineProject/core/issues/117)):

- **Instance names can't contain `@`.** Encoding names always carry
`@version`, so the two can never collide, and `"game"` versus `"jsonrpc@1"`
shows at a glance which kind of name it is. An instance can't shadow an
encoding.
- **A dependency's instances are used by qualified name,** for example
`@framing = "minecraft.game"`, so two packages speaking one legacy system
share one definition instead of copies that drift. A change to the
dependency's instance counts towards this package's bump by reach, as any
dependency change does.
- **Handshake:** packet systems usually bring their own
([runtime#18](https://github.com/ComlineProject/runtime/issues/18)), often as
a phase of its own. One that doesn't establish schema agreement makes the
Expand All @@ -423,16 +436,8 @@ definition.
a real legacy schema needs them.
- **The framing's parameters:** leaning towards named framing instances in
`config.idp` (see
[Where the framing's parameters are written](#where-the-framings-parameters-are-written));
shadowing and cross-package sharing are open.
- **`@consumer`.** What does it add beyond being the provider's complement? The
original note says the parameters "need more situational introspection".
One possible meaning: in the 2024 file, `Multiple` may originally have meant
*how many* (several instances providing or consuming a protocol, like a
fleet of servers behind the reverse proxy in `reverse_proxy.ids`), not
which direction. That reading would give `@consumer` a job of its own: who
may call a protocol, and how many of them. Not decided.
- **The family id's syntax,** which follows the function-id decision.
[Where the framing's parameters are written](#where-the-framings-parameters-are-written)).
Instance names and sharing across packages are decided.

## Prerequisites

Expand Down
Loading
Loading