Skip to content

compat: the facts each frozen version provides for comline@2 - #159

Merged
Kinflou merged 2 commits into
masterfrom
core/compat-facts
Nov 1, 2026
Merged

Kinflou merged 2 commits into
masterfrom
core/compat-facts

Conversation

@Kinflou

@Kinflou Kinflou commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Step 2 of 3 of the compatibility work (#119, ComlineProject/runtime#15): what each frozen version records and provides for comline@2.

Package id

  • id = "<32 lowercase hex>" in config.idp (128 random bits; std's randomly seeded hasher, the clock and the pid through blake3, no new dependency). Checked when config.idp is interpreted.
  • package::id::{generate, check, add_to_config_source, ensure_in_config}: the CLI's new writes one, build adds one to a config.idp without it, inserting a single line after congregation.
  • Changing the id is PackageIdChanged, major (identity). Adding one where there was none is no change.

comline_core::compat (behind compiler)

  • facts(project, version) -> PackageFacts { id, version, protocols: [{name, line, shape_hash, functions: [{name, since}]}], encodings: [{kind, name, params, param_hash}] }, serde-serializable; also facts_of_commit and facts_of_context.
  • Keyed by qualified path (namespace::Protocol): protocols of the same name in two schemas have their own facts. ProtocolFacts carries path, namespace and name. Each protocol's changes come from the diff with every other protocol left out, and its reach is taken from its own schema. A renamed namespace or protocol keeps its history (line and sinces).
  • Line: the release at which a protocol last broke. Computed against the last release with step 1's compare: a breaking change counts for a protocol when it concerns the protocol or a declaration it reaches (diff::protocol_reach, diff::concerns_protocol; dependencies by reach; thrown errors and constants named by validator arguments included). Namespace and protocol renames don't count, since names don't reach the wire. A change to the package's wire.format, wire.framing or handshake moves every line.
  • Since: the release a function first appeared in, per protocol. If a function is removed in one release and added back in a later one, its since starts again.
  • Releases only: before 1.0 a break is the 0.x minor. A build carries the line and since values of the release it leads to (0.2.0-dev.2 → 0.2.0). If comline release makes a different version (a raised bump, or --stable), it renames them in a tree of its own. The values are computed against the last release, not build by build.
  • Storage: a facts blob in each build's root tree, beside config and docs. Schema blobs and schema_ir_hash don't change. The no-change check ignores the blob, so an old store doesn't produce an empty build. A commit without the blob gets its facts computed on read. A test recomputes every commit's facts and compares them with the stored ones.
  • Shape hash (computed on demand): blake3 of canonical CBOR over the encodings in play (by parameter hash), the functions (name, arguments, return, thrown errors, @provider/@consumer) and every declaration they reach. Aliases are resolved, and declarations are numbered in the order first reached, so no type name or namespace goes in. Order counts only where it travels. Function order counts where the framing calls by index (calls), and field and variant order where the format goes by position (struct_fields, enum_variants); otherwise they're sorted by name. An encoding without an official profile counts as order-sensitive. Tests: a function reorder keeps the hash under jsonrpc and changes it under datagram@2, and a field reorder keeps it under json@1 and changes it under packed@1. Docstrings, spans, validators, constants, defaults and advisory annotations are left out. Tests show that docstring, span, namespace, alias and constant edits leave the hash alone, while field, argument, function-name and thrown-error edits change it, and only for the protocol that reaches them.
  • Encoding parameter hash: blake3 of CBOR (kind, name, params), covering each format, framing and handshake in play. A declared encoding's profile hash counts as its profile parameter.

For step 3: identifying a protocol on the wire

Facts are keyed by the qualified path, but the shape hash leaves the namespace out, as decided. How comline@2 names a protocol in its offer is step 3's question (runtime#15). It could send the path, which would break peers on a namespace rename even though the namespace doesn't travel today. Or it could send the bare name plus the shape hash, which is ambiguous when two schemas have a protocol of the same name.

core#156

A field's effective validators are compared with each call's arguments resolved against the validator's parameter defaults. A narrowed default now reports validation_tightened on every use that relies on it, and writing a default out is no change.

Not here: core#155

@open doesn't exist in the language yet: the grammar's enum takes no annotations, and runtime#17 is still open. Freezing enum annotations needs a grammar change plus an IR field, so it stays open.

Refs #119, ComlineProject/runtime#15. Closes #156.

- Package id: `id = "<32 hex>"` in config.idp, checked; `package::id`
  generates one and adds it to a config.idp minimally. Changing it is a
  major change (identity); adding one is none.
- `comline_core::compat`: each protocol's line (the release it last broke
  at, by the diff's rules, counting what it reaches, dependencies included;
  a package wire change moves every line) and each function's since,
  recorded per build as a `facts` blob in the root tree and computed for
  versions built before. Lines and sinces name releases: a build carries
  what it leads to, and a release made as another version renames them.
- Wire-shape hash per protocol (canonical CBOR, wire-reaching content only)
  and parameter hash per encoding, computed on demand.
- core#156: validator calls are compared with their arguments resolved
  against the validator's parameter defaults.

Refs #119, ComlineProject/runtime#15. Closes #156.
- Facts are keyed by `namespace::Protocol`: two protocols of the same
  name in different schemas get their own lines and sinces. Each
  protocol's changes come from the diff with every other protocol left
  out, and its reach is taken from its own schema. A renamed namespace or
  protocol keeps its history. `ProtocolFacts` gains `path` and
  `namespace`; the shape hash still leaves the namespace out.
- The shape hash keeps function order only where the framing calls by
  index, and field and variant order only where the format goes by
  position (the profiles' `calls`, `struct_fields`, `enum_variants`, as
  `WireFacts` reads them); otherwise sorted by name. An encoding without
  an official profile counts as order-sensitive.
@Kinflou
Kinflou merged commit 5db1c29 into master Nov 1, 2026
11 checks passed
@Kinflou
Kinflou deleted the core/compat-facts branch November 1, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

diff: resolve validator arguments against their defaults

1 participant