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
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
start from this file: copy it, keep the <head> as it is, and write the content inside <main>.
Styles and scripts are files in docs/templates/assets/, referenced and not embedded. In a copy, change the
"assets/" prefix of each href and src to the relative path from the copy to that directory, for example
"../../../docs/templates/assets/" from openspec/changes/<name>/. See AGENTS.md, "Specs and plans are HTML
"../../../../docs/templates/assets/" from openspec/changes/<name>/. See AGENTS.md, "Specs and plans are HTML
documents". Code goes in <pre><code class="language-rust"> (or toml, json, yaml, bash, diff). Give sections an
id and list them in nav.toc.
-->
<link rel="stylesheet" href="../../../docs/templates/assets/agent-document.css">
<link rel="stylesheet" href="../../../docs/templates/assets/highlight/styles/github.min.css" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="../../../docs/templates/assets/highlight/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<script defer src="../../../docs/templates/assets/highlight/highlight.min.js"></script>
<link rel="stylesheet" href="../../../../docs/templates/assets/agent-document.css">
<link rel="stylesheet" href="../../../../docs/templates/assets/highlight/styles/github.min.css" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="../../../../docs/templates/assets/highlight/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<script defer src="../../../../docs/templates/assets/highlight/highlight.min.js"></script>
<body>
<main>
<h1>rust-extension-mep-0-2: design</h1>
<p class="meta">OpenSpec change · Bead <code>morphir-0xtc</code> · 2026-10-31 · proposed</p>
<p class="meta">OpenSpec change · Bead <code>morphir-0xtc</code> · 2026-10-31 · released as <code>extension/rust/v0.4.0</code></p>

<nav class="toc" aria-label="Contents">
<ul>
Expand Down Expand Up @@ -111,7 +111,10 @@ <h3 id="rel-1">REL-1. Release 0.4.0 with the existing workflow</h3>
<code>modelTransport</code>. The binding version moves to <code>0.4.0</code> (a new minor: a new protocol
line). The tag <code>extension/rust/v0.4.0</code> on the merge commit starts the existing release workflow,
which publishes the WASM, <code>.wasm.sha256</code> and <code>release.json</code>. The dry run is the same
resolution, build and asset validation, run locally on the merged commit before the tag.</p>
resolution, build and asset validation, run locally on the merged commit before the tag. It checks the release
steps, not the published digest: the release job builds on another machine, and the WASM embeds absolute build
paths, so the published WASM differs from the local one (<code>morphir-0xtc.5</code>). The qualification
therefore runs again on the published assets.</p>

<h2 id="risks">Risks and trade-offs</h2>
<table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@

- [x] 4.1 Bump `morphir-rust-binding` to `0.4.0`, update `Cargo.lock`, the README version references and the
changelog release section; verify `cargo test --locked -p morphir-rust-binding` passes.
- [ ] 4.2 Open the pull request, get CI green and handle each verified review finding.
- [ ] 4.3 Dry run on the merge candidate: `.github/scripts/extension_release.py` for the tag,
- [x] 4.2 Open the pull request, get CI green and handle each verified review finding.
- [x] 4.3 Dry run on the merge candidate: `.github/scripts/extension_release.py` for the tag,
`mise run extension:artifact:rust` from a clean checkout and `select_extension_assets.py
--validate-short-id rust`; record the asset digests.
- [ ] 4.4 After approval, merge and tag `extension/rust/v0.4.0` on the merge commit; verify the release
- [x] 4.4 After approval, merge and tag `extension/rust/v0.4.0` on the merge commit; verify the release
workflow publishes the WASM, `.wasm.sha256` and `release.json`.
- [ ] 4.5 Verify the published assets: digests equal the dry run, and `extension describe` on the
downloaded bundle from the MoonBit native CLI reports MEP `0.1, 0.2.0-draft.1`.
- [x] 4.5 Verify the published assets, and qualify the published bytes. The published WASM digest does not
equal the local dry run, because the two builds ran on different machines (follow-up `morphir-0xtc.5`), so
run the qualification of group 3 and `mise run test:cli-release rust` again on the downloaded assets.
`extension describe` from the MoonBit native CLI reports MEP `0.1, 0.2.0-draft.1`.

## Workflow follow-up

Expand Down
95 changes: 95 additions & 0 deletions openspec/specs/rust-extension-model-operations/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# rust-extension-model-operations Specification

## Purpose

Defines how the Rust extension (`morphir-rust`) takes part in MEP `0.2.0-draft.1`: which protocol versions and
model transport tuples it declares, how it compiles Rust source into a model envelope and generates Rust from
one, and which requests it refuses.

## Requirements

### Requirement: Declared protocol versions
The extension SHALL declare MEP `0.2.0-draft.1` and MEP `0.1` in its describe claims, in its release
descriptor claims and in its registry entry. It SHALL select `0.2.0-draft.1` only when the host offers that
version together with a model transport offer, and SHALL select `0.1` otherwise when the host offers `0.1`.

#### Scenario: A host that offers only MEP 0.1
- **WHEN** a host initializes the extension with protocol versions `["0.1"]`
- **THEN** the extension selects `0.1`
- **AND** the initialize result has no `modelTransport` member

#### Scenario: A host that offers MEP 0.2.0-draft.1 with a model transport offer
- **WHEN** a host initializes the extension with protocol versions `["0.2.0-draft.1", "0.1"]` and a model
transport offer that contains the extension's tuples
- **THEN** the extension selects `0.2.0-draft.1`
- **AND** the initialize result's `modelTransport` holds the tuples that both peers support, in the host's
order, and the smaller of each limit

#### Scenario: A host that offers MEP 0.2.0-draft.1 without a model transport offer
- **WHEN** a host initializes the extension with protocol versions `["0.2.0-draft.1", "0.1"]` and no model
transport offer
- **THEN** the extension selects `0.1`

### Requirement: Advertised model transport tuples
The extension SHALL advertise exactly two model transport tuples, both with profile `morphir-ir`, codec
`morphir-json`, envelope version `0.1.0-draft.1` and IR version `4.0.0`: one for
`morphir.frontend.compileModel` and one for `morphir.backend.generateModel`. It SHALL NOT advertise an Ion
tuple and SHALL NOT advertise a guarantee.

#### Scenario: Describe claims list the tuples
- **WHEN** a host sends `morphir.extension.describe`
- **THEN** `capabilities.modelTransport.tuples` holds exactly the two tuples
- **AND** `capabilities.modelTransport.guarantees` is empty

#### Scenario: A host that offers only Ion
- **WHEN** a host offers MEP `0.2.0-draft.1` with only `morphir-model` / `ion-binary` tuples
- **THEN** the agreed model transport holds no tuple, so no model operation is available in the session

### Requirement: Compile Rust source into a model envelope
For an agreed `morphir.frontend.compileModel` tuple, the extension SHALL compile the request's source with the
same rules as `morphir.frontend.compile` at IR v4. A successful result SHALL carry a model envelope whose
payload is the IR v4 JSON document that `morphir.frontend.compile` returns for the same source, with the
envelope's tuple, `modelId` equal to the package name and `revision` equal to `sha256:` and the payload digest.

#### Scenario: A supported source file
- **WHEN** a host sends `morphir.frontend.compileModel` for one supported `.rs` document
- **THEN** the result has `success: true`, the compiled module names and a model envelope
- **AND** the decoded payload equals the recorded golden IR v4 document

#### Scenario: A source file outside the supported subset
- **WHEN** a host sends `morphir.frontend.compileModel` for a document that the frontend rejects
- **THEN** the result has `success: false` and the frontend's diagnostics
- **AND** the result has no `model` member

### Requirement: Generate Rust from a model envelope
For an agreed `morphir.backend.generateModel` tuple, the extension SHALL decode the model envelope's payload
and generate Rust with the same rules as `morphir.backend.generate`, for target `rust`.

#### Scenario: A model envelope from compileModel
- **WHEN** a host sends `morphir.backend.generateModel` with the envelope that `compileModel` returned
- **THEN** the result has `success: true` and the artifacts that `morphir.backend.generate` returns for the
payload's IR
- **AND** the generated `lib.rs` equals the recorded golden output

### Requirement: Refuse requests outside the agreed tuple
The extension SHALL refuse a model operation whose tuple was not agreed in the session, whose envelope does
not match the tuple, or whose payload does not match its length or SHA-256, with a JSON-RPC error and without
running the frontend or backend. It SHALL refuse a generation that requires a guarantee.

#### Scenario: A tuple that was not agreed
- **WHEN** a host sends `morphir.backend.generateModel` with an Ion tuple, or with an IR version other than
`4.0.0`
- **THEN** the response is a JSON-RPC error and has no result

#### Scenario: A model envelope with another envelope version
- **WHEN** a host sends `morphir.backend.generateModel` with a model whose `envelopeVersion` is not
`0.1.0-draft.1`
- **THEN** the response is a JSON-RPC error and has no result

#### Scenario: A model operation on the MEP 0.1 line
- **WHEN** a session selected MEP `0.1` and the host sends `morphir.frontend.compileModel`
- **THEN** the response is a JSON-RPC error

#### Scenario: A generation that requires a guarantee
- **WHEN** a host sends `morphir.backend.generateModel` with a required guarantee
- **THEN** the extension does not generate artifacts
Loading