Skip to content

Extensibility: third-party formats, framings, transports and dispatchers #120

Closed

Description

@Kinflou

How developers extend Comline with their own wire formats, framings, transports and dispatchers, and what separates an official extension from a third-party one. Proposed, open for discussion; nothing implemented. The full write-up is the proposal page in ComlineProject/docs#48.

Where things stand

  • Transports: open. Rust's Transport and TS's equivalent take anything that moves bytes.
  • Wire formats: open at runtime (WireFormat in Rust, Codec in TS). contract/wire.rs already asks third-party formats to namespace their names.
  • Framings: the runtime trait is open, but generated code isn't. Generators bake the framing in and know only datagram and jsonrpc. Core checks @framing against that list, so a third-party framing can't be named in a schema. An unknown default_framing silently becomes datagram (cli#81).
  • Dispatchers: the generated <Proto>Dispatcher implements Dispatch, and wrapping it works, but that isn't documented as an extension point.
  • Generators: compiled into the CLI (Generators outside the CLI build: loading, discovery, compatibility #8). Process plugins (Proposal D) are blocked by the playground.
  • Nothing lets a schema say "this protocol uses com.acme.frame@1, and here is what that means".

Proposal

  1. Official encodings use the same public mechanism as third-party ones. "Official" means maintained here and shipped by default, not built in through a private path, the same principle as std being a package.
  2. An encoding is a definition: a versioned name (bare for official, namespaced otherwise), a profile (Compatibility rules: what a version bump means #119, encoding profiles) and vectors in the corpus format. Implementations are per language and separate. The corpus format and the profile schema become a public, versioned spec. The conformance runner becomes a library a third party runs in their own CI.
  3. Core learns about a third-party encoding from a dependency. A package declares the encodings it defines (name plus profile) in config.idp. A dependent package can then use those names in @framing and in its declared formats. The profile is trusted like the dependency's schemas: declared, and proven in the author's CI. An encoding with no profile gets the strictest one.
  4. Generated code gets the implementation from the consumer's config. comline.toml maps each name to a type per target, e.g. "com.acme.frame@1" = "acme_frame::AcmeFraming". The generator needs no plugin to wire it. A declared but unmapped encoding is a generation error, never a fallback. The per-package format trait (generation#67) is implemented for each mapped name instead of being sealed.
  5. A format serializes the generated data model (serde in Rust, Shapes in TS). Needing more from the schema, such as tags or ordinals, makes it a language feature, not a plug-in.
  6. Transports and dispatchers stay runtime-only: no name, profile or declaration, since they can't affect compatibility. They get documented patterns (middleware by wrapping Dispatch).
  7. Generators stay separate (Generators outside the CLI build: loading, discovery, compatibility #8 / Proposal D). Nothing above needs runtime generator loading.

Promotion path: a third-party encoding becomes official by contributing its profile, vectors and implementations to the official runtimes. It keeps its namespaced name, because renaming would change the handshake. Bare names are reserved for encodings that start out official.

Open questions

  • Trust: is "declared by a dependency" enough, or should core require the dependency to ship vectors too, and could a CI tool run a third-party implementation against them?
  • Where the name → type mapping lives: comline.toml per target (consumer-side), or also a default shipped by the encoding's package per language.
  • Whether a third-party encoding package can also carry its implementations (a crate, an npm package) and have generated lib-mode output depend on them automatically.
  • Whether the same mechanism should cover transports later (for example, a handshake capability naming the transport).

Work (after discussion)

Related: #8, #117, #119, generation#67, cli#81, runtime#14.

Activity

  1. Kinflou commented on Oct 19, 2026

    @Kinflou
    MemberAuthor

    Related: #123 (packet dispatch) is the first concrete use of a framing that needs schema-level addressing (explicit ids, families, phases). That's the case the "what a format can't be" rule sends to the language rather than to a plug-in. Its official comline.packet@1 and any custom packet framing would read generated dispatch tables.

  2. Kinflou commented on Oct 20, 2026

    @Kinflou
    MemberAuthor

    Decided 2026-10-20 (recorded in ComlineProject/docs#64):

    • Trust: official encodings must prove everything (vectors for every profile fact, implementations for every official target, CI conformance, a spec page). Third-party with vectors: conformance proven via a check like comline conformance. Without vectors: encoding conformance is given up (@unverified + consumer acceptance).
    • Default mapping: an encoding's package can suggest the implementing type per language; the consumer's comline.toml only overrides it. Declared but neither mapped nor suggested is a generation error.
    • Shipping implementations waits for the third-party half of dependency publishing.
  3. Kinflou commented on Oct 31, 2026

    @Kinflou
    MemberAuthor

    Decided 2026-10-31:

    • Generators outside the CLI use one stdio protocol. The compiled CBOR IR and the generation request go in on stdin, and the generated files come out on stdout.
      • A generator ships as a native executable or as a WASI .wasm. The CLI runs a .wasm under wasmtime; the playground runs it through a browser WASI shim.
      • A generator declares which IR format stamps and protocol version it accepts, and the CLI refuses a mismatch.
    • The official Rust and TypeScript generators use the same request and response types, though they stay linked into the CLI.
    • An encoding package's suggested mapping carries the implementation's package name and version. Generated lib output depends on it automatically, and a consumer can override it in comline.toml.
    • Transports stay invisible to the handshake.

    The extension API and the generator protocol are part of the 1.0 promise (docs conduct/readiness.md). An implementation plan comes next.

  4. added
    extensibilityThird-party encodings and generators
    1.0-blockerMust be done before the 1.0 announcement (docs conduct/readiness.md)
    P0Now: top priority
    and removed on Oct 31, 2026
  5. added this to the 1.0 milestone on Oct 31, 2026
  6. Kinflou commented on Oct 31, 2026

    @Kinflou
    MemberAuthor

    Status (2026-11-01): the guides shipped in ComlineProject/docs#105, and comline conformance in ComlineProject/cli#108. This stays open for #147: third-party encodings and handshakes currently give up guarantees silently.

  7. Kinflou commented on Oct 31, 2026

    @Kinflou
    MemberAuthor

    Done:

    Follow-ups: #149 (an encoding's own unverified reason), ComlineProject/language-server#88, ComlineProject/comline-typescript#98 and #99.

  8. moved this from Todo to Done in Comline Development Roadmapon Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    1.0-blockerMust be done before the 1.0 announcement (docs conduct/readiness.md)P0Now: top priorityenhancementNew feature or requestextensibilityThird-party encodings and generators

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions