Skip to content

Freeze readiness: nothing a shared contract may still move is frozen at 1.0.0 - #92

Merged
HackTuah merged 1 commit into
mainfrom
slice/047-freeze-readiness
Oct 20, 2026
Merged

HackTuah merged 1 commit into
mainfrom
slice/047-freeze-readiness

Conversation

@HackTuah

Copy link
Copy Markdown
Member

1.0.0 freezes this package's own call surface. This pull request makes everything a protocol revision, a shared encoding or a system beside this one may still extend either open to growth or versioned, so that 1.0.0 does not lock it. Each change was red before its fix.

Two documented breaks (CHANGELOG, each with "How to tell whether you are affected"):

  • The host contract: dispatch's third argument is the host's dispatch_opts plus a reserved :mcp key holding the request's context: params._meta as sent, the client's capabilities, and the revision the request is served under. A host that matches or validates the whole list exactly, or passes its own :mcp, is affected.
  • The exported bytes: resource templates become :resource_template nodes, and the canonical bytes move to schema_version 4. The envelope's shape is unchanged, and bytes at 3 keep verifying: 0.6.0 to 0.10.1's goldens are kept and checked under the new code.

Also:

  • Wire bytes: keys are sorted at every depth, so a response's bytes no longer depend on the OTP release. One tools/list with a 40-property schema hashed differently on OTP 28.5.0.8 and 29.1; it now hashes the same on both, and the hash is pinned.
  • docs/api-stability.md, "What may grow, and how": a growth table (eight unions, all OPEN) held by a census over the compiled types; encodings versioned, not frozen; one reserved key for request context; stable wire bytes; callbacks added only as optional.
  • Will-not-implement: entry 13 (no change notification advertised that is not delivered); entry 12 now says "never input_required".

Evidence

  • GATE_LEG=pinned ./tools/gate.sh: "Gate OK." on OTP 28.1.1 with Elixir 1.18.4.
  • The wire hashes agree on OTP 28.5.0.8 and 29.1, both with Elixir 1.20.4.
  • Every census added here was shown red against a planted violation.

Not yet measured

  • OTP 27. This run is that measurement.

…at 1.0.0

1.0.0 freezes this package's own call surface. This makes everything a
protocol revision, a shared encoding or a system beside this one may
still extend either open to growth or versioned, so 1.0.0 does not lock
it. Each change was red before its fix.

- Wire bytes. Every JSON value the transports write has its object keys
  in sorted order at every depth, through one internal writer
  (BeamMCP.JSON.encode!/2, not public). A refusal walks a client's
  arguments in sorted order too. Before, maps of more than 32 keys were
  written in hash order: one tools/list with a 40-property schema hashed
  e6d4e13e... on OTP 28.5.0.8 and 09fd2985... on OTP 29.1. Now both give
  40d948fc..., pinned on every leg.
- Request context (BREAKING, the host contract). Dispatch's third
  argument is the host's dispatch_opts plus a reserved :mcp key: the
  request's params._meta as sent, the client's capabilities and the
  revision the request is served under. One request's _meta never
  reaches the next. new/1 and the HTTP transport's init refuse a host's
  own :mcp.
- The resource_template node kind (BREAKING, the exported bytes). A
  resource template is a node of the declared connectome, identified by
  its uri template. Before, it was enumerated as unreadable. The
  canonical bytes move to schema_version 4 because their node vocabulary
  grew. The envelope's shape is unchanged, and bytes at 3 still verify:
  0.6.0 to 0.10.1's goldens are kept and checked under the new code.
- docs/api-stability.md, "What may grow, and how": a growth table marks
  every named atom-union type and struct field OPEN or CLOSED (all eight
  are OPEN), held by a census over the compiled types. The section also
  covers versioned encodings, the :mcp key, stable wire bytes, and
  callbacks added only as optional. BeamMCP.JSON.decode/1 leaves every
  error reason but its two named ones unspecified.
- Will-not-implement entry 13: no listChanged or subscribe is advertised
  while subscriptions/listen is not served; both are answered -32601, and
  404 over HTTP. Entry 12 now draws its line at input_required rather
  than "only complete".

Measured locally: the full gate on OTP 28.1.1 with Elixir 1.18.4 prints
"Gate OK"; the wire hashes agree on OTP 28.5.0.8 and 29.1. OTP 27 has
not been run locally; this pull request's CI is that measurement.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
@HackTuah
HackTuah force-pushed the slice/047-freeze-readiness branch from f7bbd29 to df3f761 Compare October 20, 2026 12:36
@HackTuah
HackTuah merged commit fe78c3a into main Oct 20, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant