From bb3f7b25ba9a65db3de0c46ecca1e18b7d5609cc Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:42:16 -0500 Subject: [PATCH 01/10] test(cli): qualify the published extensions on the worker host Add scripts/extension-qualification.mbtx and its driver apps/morphir/scripts/extension-qualification.ts. They run the Elm extension v0.4.1, the Python extension 0.4.1 and 0.4.2, the MoonBit guest (native and portable) and a new morphir-extension-sdk fixture guest from the native and npm CLIs, and write one receipt per suite. Downloads go into new directories and are used only after their SHA-256 equals the pin in apps/morphir/fixtures/qualification. Rewrite test:cli-extism-python, test:portable-mep-cli and test:cli-native-prerequisites for 0.2.0 pins on the worker host, and add test:cli-extension-elm, test:extension-conformance and test:extension-qualification. All accept --json. They stay manual. --- .config/mise/config.toml | 24 +- .gitignore | 2 + apps/morphir/docs/extension.md | 82 +- .../fixtures/elm-extension/v1/README.md | 6 + .../fixtures/elm-extension/v1/src/Rules.elm | 15 + .../fixtures/extism-python/v1/README.md | 13 +- .../fixtures/extism-python/v1/single/rules.py | 12 + .../fixtures/qualification/published.json | 109 ++ apps/morphir/fixtures/sdk-guest/Cargo.lock | 1523 +++++++++++++++++ apps/morphir/fixtures/sdk-guest/Cargo.toml | 27 + apps/morphir/fixtures/sdk-guest/README.md | 46 + .../fixtures/sdk-guest/build-receipt.json | 56 + .../sdk-guest/morphir-sdk-fixture-guest.wasm | Bin 0 -> 752806 bytes apps/morphir/fixtures/sdk-guest/src/lib.rs | 358 ++++ .../extension-qualification-consumer.py | 31 + .../scripts/extension-qualification.ts | 741 ++++++++ apps/morphir/scripts/extism-python-api.py | 42 - apps/morphir/scripts/test-extism-python.ts | 86 - docs/MISE_TASKS.md | 7 +- scripts/extension-qualification.mbtx | 451 +++++ scripts/extism-python.mbtx | 711 -------- scripts/portable-mep-cli.mbtx | 708 -------- tests/wasm-host/README.md | 2 +- tools/portable-command-json.test.ts | 49 +- 24 files changed, 3482 insertions(+), 1619 deletions(-) create mode 100644 apps/morphir/fixtures/elm-extension/v1/README.md create mode 100644 apps/morphir/fixtures/elm-extension/v1/src/Rules.elm create mode 100644 apps/morphir/fixtures/extism-python/v1/single/rules.py create mode 100644 apps/morphir/fixtures/qualification/published.json create mode 100644 apps/morphir/fixtures/sdk-guest/Cargo.lock create mode 100644 apps/morphir/fixtures/sdk-guest/Cargo.toml create mode 100644 apps/morphir/fixtures/sdk-guest/README.md create mode 100644 apps/morphir/fixtures/sdk-guest/build-receipt.json create mode 100755 apps/morphir/fixtures/sdk-guest/morphir-sdk-fixture-guest.wasm create mode 100644 apps/morphir/fixtures/sdk-guest/src/lib.rs create mode 100644 apps/morphir/scripts/extension-qualification-consumer.py create mode 100644 apps/morphir/scripts/extension-qualification.ts delete mode 100644 apps/morphir/scripts/extism-python-api.py delete mode 100644 apps/morphir/scripts/test-extism-python.ts create mode 100644 scripts/extension-qualification.mbtx delete mode 100644 scripts/extism-python.mbtx delete mode 100644 scripts/portable-mep-cli.mbtx diff --git a/.config/mise/config.toml b/.config/mise/config.toml index 6ebd67c9d..62c7eab21 100644 --- a/.config/mise/config.toml +++ b/.config/mise/config.toml @@ -294,16 +294,28 @@ description = "Run and recheck the independent pinned shared MEP guest suite" run = "moonx scripts/portable-mep-mck.mbtx" [tasks."test:portable-mep-cli"] -description = "Run the saved MEP guest through the primary CLI extension command" -run = "moonx scripts/portable-mep-cli.mbtx" +description = "Qualify the portable MoonBit guest (morphir-bytes-v1 WASM) on the worker host from the native and npm CLIs, with the shared MEP kit; [--json]" +run = "moonx scripts/extension-qualification.mbtx moonbit-portable" [tasks."test:wasm-workers"] description = "Run the package tests of one target with the end-to-end WASM worker tests: --target native|js [--scope workers] [--json]" run = "moonx scripts/wasm-workers-test.mbtx" [tasks."test:cli-extism-python"] -description = "Qualify an explicit local Python Extism bundle through native and packaged CLI entry points" -run = "moonx scripts/extism-python.mbtx" +description = "Qualify the pinned Python extension 0.4.1 and 0.4.2 releases on the worker host from the native and npm CLIs; [--python PATH] [--json]" +run = "moonx scripts/extension-qualification.mbtx python" + +[tasks."test:cli-extension-elm"] +description = "Qualify the pinned Elm extension v0.4.1 executables on the worker host from the native and npm CLIs; [--json]" +run = "moonx scripts/extension-qualification.mbtx elm" + +[tasks."test:extension-conformance"] +description = "Run the morphir-extension-sdk conformance fixture guest on the worker host from the native and npm CLIs; [--json]" +run = "moonx scripts/extension-qualification.mbtx conformance" + +[tasks."test:extension-qualification"] +description = "Run every worker host qualification suite (Elm, Python, MoonBit native and portable, SDK fixture); [--python PATH] [--json]" +run = "moonx scripts/extension-qualification.mbtx all" [tasks."sdk-spec:generate"] description = "Regenerate pkgs/morphir-sdk-spec/sdk_data.mbt from pkgs/morphir-sdk-spec/sdk.scm" @@ -506,8 +518,8 @@ description = "Smoke-test CLI targets and installed package" run = "moonx scripts/typescript-tooling.mbtx test-cli" [tasks."test:cli-native-prerequisites"] -description = "Focused native source-CLI compile, provider and local repository acceptance; requires explicit --shared or MORPHIR_SHARED_SOURCE" -run = "moon run scripts/native-services.mbtx -- --acceptance" +description = "Qualify the native MoonBit guest as a process worker on the worker host from the native and npm CLIs, with the shared MEP kit; [--json]" +run = "moonx scripts/extension-qualification.mbtx moonbit-native" [tasks."check:typescript"] description = "Install frozen dependencies and check all TypeScript projects" diff --git a/.gitignore b/.gitignore index 6e9d5fce6..ebafe7ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ target **/*.mbt.js **/*.wat **/*.wasm +# The SDK conformance fixture guest is stored with its source and build receipt. +!apps/morphir/fixtures/sdk-guest/morphir-sdk-fixture-guest.wasm _build/ pkgs/morphir-host/embedding/dist/ diff --git a/apps/morphir/docs/extension.md b/apps/morphir/docs/extension.md index 8610a7702..2b7d29ebc 100644 --- a/apps/morphir/docs/extension.md +++ b/apps/morphir/docs/extension.md @@ -433,10 +433,58 @@ session cases: MEP `0.1` compile from claimed sources, an unclaimed operation, a unsuccessful answer, a failed shutdown, cancellation during generate and the deadline. -`mise run test:portable-mep-cli`, `mise run test:cli-native-prerequisites` and -`mise run test:cli-extism-python` still write retired pins. They are rewritten for -contract `0.2.0` pins and the worker host when the published extensions are -qualified on it (group 7 of the OpenSpec change `host-extensions-as-workers`). +### Published extensions and fixture guests + +The published extensions are examples of conformant extensions, not the +definition of support. `scripts/extension-qualification.mbtx` runs them, and an +independent fixture guest, from the native CLI and from the npm CLI, as a user +installs it (an unpacked `npm pack` of `apps/morphir`). Every task accepts +`--json`: + +| Task | Suite | What it checks | +| --- | --- | --- | +| `mise run test:cli-extension-elm` | `elm` | Elm extension `v0.4.1` executables: describe with the release descriptor, compile of `apps/morphir/fixtures/elm-extension/v1`, and no `node` on the PATH of the native run | +| `mise run test:cli-extism-python` | `python` | Python extension `0.4.1`: one document for IR 3 and 4, and the refusal of two documents. `0.4.2`: compile of the two-module fixture for IR 3 and 4, generate, a consumer check of the generated Python, recompile of the generated files to the same IR, and the IR version check | +| `mise run test:cli-native-prerequisites` | `moonbit-native` | The native MoonBit guest as a process worker: the shared kit's source, consumer and JSON artifacts through both CLIs, and the shared MEP kit run against the guest | +| `mise run test:portable-mep-cli` | `moonbit-portable` | The portable MoonBit guest (`morphir-bytes-v1`): the same checks, with the shared kit run through `morphir extension worker` of the native CLI | +| `mise run test:extension-conformance` | `conformance` | The `morphir-extension-sdk` fixture guest in `apps/morphir/fixtures/sdk-guest` (see its README): build receipt, WASI and Morphir host function imports, claims of a language that no host knows, compile, generate, recompile, host function answers and guest diagnostics | +| `mise run test:extension-qualification` | `all` | All five suites | + +Each run builds the CLIs and the guests that it needs. It downloads each +pinned artifact of `apps/morphir/fixtures/qualification/published.json` into a +new, empty directory under `.dev/extension-qualification/downloads` and uses it +only when its SHA-256 equals the pin. It writes one receipt for each suite, +`.dev/extension-qualification/evidence/.json` (`--evidence DIR` selects +another directory). A receipt names the platform, the CLI build (commit, a dirty +flag and the program SHA-256) and one row for each operation: extension, +version, CLI (`native`, `npm` or `mck` for the shared kit runner), operation, +result, guest SHA-256, descriptor SHA-256 and the digests of the published +files. With `--json`, stdout is one `morphir/command-result` with `result.suites` +(state and row counts of each receipt) and `result.receipts`; build and driver +output goes to stderr. A failed suite exits 1 with `qualification_failed`. + +A row is `passed`, `failed` or `known-failure`. A known failure is an operation +that fails for a recorded cause outside the host, with its finding and +follow-up bead. It does not fail the suite, and it fails the suite when it +passes, so the record is removed. The current known failures are: + +- Elm extension `v0.4.1` compile. The extension requires + `package.exposedModules` in `morphir.frontend.compile`; the MEP model makes it + optional and the host, which conforms, does not send it. Describe passes. + Follow-up: bead `morphir-ukvn.5.64.36.16` (finos/morphir-elm). +- The shared MEP kit against the MoonBit guest. The kit + `spec/mep/mck/guest-moonbit-bool.json` asks for the frontend profile + `moonbit-model-bool-library-v1`, which the guest retired, so the case + `compile-ion` fails after describe, initialize and the notifications pass. + Follow-up: bead `morphir-ukvn.5.64.36.17` (finos/morphir). For the same + reason `mise run test:portable-mep-mck` does not pass. + +No shared kit profile fits the SDK fixture guest, because each shared profile +names its own extension. The `conformance` suite is therefore a local check. A +generic kit profile is bead `morphir-ukvn.5.64.36.15`. + +These tasks are manual. No pull request or merge-queue job runs them, because +they download published releases and build several guests. ## Controlled local repositories @@ -484,24 +532,8 @@ Every lifecycle command accepts `--json` and writes one `morphir/command-result` after required writes. These commands do not automatically join installed selection to invocation. That downstream acceptance remains morphir-ukvn.5.64.30. -**Pending.** `mise run test:cli-native-prerequisites` still writes retired pins and -calls the removed helper route, so it does not pass against this CLI. It is -rewritten for contract `0.2.0` pins and the worker host in group 7 of the OpenSpec -change `host-extensions-as-workers`; see [Qualification](#qualification). Until -then, the description below records what the task covered before the worker host: - -```sh -mise run test:cli-native-prerequisites -- --shared /path/to/finos/morphir --json -``` - -It freezes separately built native guests and fault peers, tests embedded Boolean -and Elm compilation, compile/retain/generate/consumer execution, malformed/oversized -frames, tuple and context refusal, crashes, diagnostics, deadlines, cancellation, -shutdown and cleanup. It also tests local publication/install, a real version -update, corrupt and disabled selections, offline replay and removal in a fresh -home. It reruns the existing portable provider tests and CLI unit suite. The task -passes the produced CLI, adapter and guest paths explicitly to each test process, -binding its receipts to tested artifacts even when the caller has artifact -override variables set. Evidence and host pins stay under `.dev/native-services`; -no installed matrix, new CI job, -release or default switch is performed. +`mise run test:cli-native-prerequisites` used to test these commands with the +retired helper route. It now qualifies the native MoonBit guest on the worker +host; see [Qualification](#qualification). The Rust adapter acceptance stays +available as `moon run scripts/native-services.mbtx -- --acceptance --shared +/path/to/finos/morphir --json` until the helper routes are removed. diff --git a/apps/morphir/fixtures/elm-extension/v1/README.md b/apps/morphir/fixtures/elm-extension/v1/README.md new file mode 100644 index 000000000..5654a4612 --- /dev/null +++ b/apps/morphir/fixtures/elm-extension/v1/README.md @@ -0,0 +1,6 @@ +# Elm extension qualification fixture + +`src/Rules.elm` is one Elm module for `mise run test:cli-extension-elm`. The Elm +extension `v0.4.1` does not claim `multiDocument`, so the fixture is one +document. Its module name has one segment because the extension compiles only +module names that its package name does not prefix. diff --git a/apps/morphir/fixtures/elm-extension/v1/src/Rules.elm b/apps/morphir/fixtures/elm-extension/v1/src/Rules.elm new file mode 100644 index 000000000..ead69e1a7 --- /dev/null +++ b/apps/morphir/fixtures/elm-extension/v1/src/Rules.elm @@ -0,0 +1,15 @@ +module Rules exposing (eligible, priority) + + +eligible : Bool -> Bool -> Bool +eligible active vip = + active && not vip + + +priority : Bool -> Int +priority vip = + if vip then + 1 + + else + 2 diff --git a/apps/morphir/fixtures/extism-python/v1/README.md b/apps/morphir/fixtures/extism-python/v1/README.md index 6e165362c..ce6dbc151 100644 --- a/apps/morphir/fixtures/extism-python/v1/README.md +++ b/apps/morphir/fixtures/extism-python/v1/README.md @@ -10,7 +10,12 @@ The local `large_value` function is an additional precision regression. Its integer exceeds JavaScript's exact-number range and must survive generation without rounding. -The focused CLI suite compares native Python API compilation with the real -published WASM bundle for IR 3 and 4, generates Python, checks selected consumer -behavior, and recompiles without changing the IR. This proves only this bounded -subset and the exact host/CLI cells in the resulting receipt. +`single/rules.py` is one module with no imports, for the Python extension +`0.4.1`, which does not claim `multiDocument` and compiles one document for each +request. The two-module `acme` directory is its multi-document refusal. + +`mise run test:cli-extism-python` compiles `acme` with the published `0.4.2` +module for IR 3 and 4 from the native and npm CLIs, generates Python, runs +`apps/morphir/scripts/extension-qualification-consumer.py` on the generated +files, and recompiles them without a change to the IR. This proves only this +bounded subset and the exact host and CLI cells in the resulting receipt. diff --git a/apps/morphir/fixtures/extism-python/v1/single/rules.py b/apps/morphir/fixtures/extism-python/v1/single/rules.py new file mode 100644 index 000000000..e82de9cce --- /dev/null +++ b/apps/morphir/fixtures/extism-python/v1/single/rules.py @@ -0,0 +1,12 @@ +from __future__ import annotations + + +def choose(flag: bool, first: int, second: int) -> int: + if flag: + return first + else: + return second + + +def large_value() -> int: + return 9007199254740993 diff --git a/apps/morphir/fixtures/qualification/published.json b/apps/morphir/fixtures/qualification/published.json new file mode 100644 index 000000000..d228c72f7 --- /dev/null +++ b/apps/morphir/fixtures/qualification/published.json @@ -0,0 +1,109 @@ +{ + "contractVersion": "0.1.0", + "elm": { + "repository": "finos/morphir-elm", + "tag": "extension/elm/v0.4.1", + "version": "0.4.1", + "sourceRevision": "a3299f95c9252852f19c1ccabbb0d1525a24a25e", + "descriptor": { + "filename": "morphir-elm-extension-0.4.1.bundle.release.json", + "sha256": "152ee03ddd4e9e0b91fbe0d6a79f6228387ce35c3f42a32f844ababf0c1c924d" + }, + "executables": { + "aarch64-apple-darwin": { + "filename": "morphir-elm-extension-0.4.1-aarch64-apple-darwin", + "sha256": "7b4ea687589c67e561162cc1cf7cac78d93d49cfc2dfc4e6fd17217c6b7139e9" + }, + "x86_64-apple-darwin": { + "filename": "morphir-elm-extension-0.4.1-x86_64-apple-darwin", + "sha256": "5ed08c452bc70ab8c70b3cdcfb50271e3e828a840cb77b214a799a4718c98196" + }, + "aarch64-unknown-linux-gnu": { + "filename": "morphir-elm-extension-0.4.1-aarch64-unknown-linux-gnu", + "sha256": "b9394d624b6962be93272b587c35379fb7ccab2468ed525e819233e8047ebd72" + }, + "x86_64-unknown-linux-gnu": { + "filename": "morphir-elm-extension-0.4.1-x86_64-unknown-linux-gnu", + "sha256": "2f172a7f3979962ac1cb0203457a0b8178324712c8b58e21c578c68d626d46bb" + }, + "aarch64-pc-windows-msvc": { + "filename": "morphir-elm-extension-0.4.1-aarch64-pc-windows-msvc.exe", + "sha256": "1fa57c943b209c2065499f2dcd93d2f5039d1fe82a22b2292a7d7253e660a97b" + }, + "x86_64-pc-windows-msvc": { + "filename": "morphir-elm-extension-0.4.1-x86_64-pc-windows-msvc.exe", + "sha256": "5049f4ab97671ef54d183157c82b13a6687b6de5da33566756ae4c3e627d49d8" + } + } + }, + "python": { + "repository": "finos/morphir-python", + "releases": { + "0.4.1": { + "tag": "extension/python/v0.4.1", + "sourceRevision": "0d7e2eb62b4b1249129ab702a5240c41266813f6", + "module": { + "filename": "morphir-python-binding-0.4.1.wasm", + "sha256": "a04722e16115667fc7c332177805dcb049aef563afcee0e5f80c5fea302f5b4e" + }, + "descriptor": { + "filename": "morphir-python-binding-0.4.1.release.json", + "sha256": "6cebee18b70e75da90f1cd187f24c8185ddde2565bb7c8e5049e21bbcccf0bb3" + } + }, + "0.4.2": { + "tag": "extension/python/v0.4.2", + "sourceRevision": "1f547d17f648d5b398862f9dfceb05b6d463cb3f", + "module": { + "filename": "morphir-python-binding-0.4.2.wasm", + "sha256": "10acafa0f0282c5240e9620a5f2bb21629cc7580b9add528e7b77c7e54db22b5" + }, + "descriptor": { + "filename": "morphir-python-binding-0.4.2.release.json", + "sha256": "72770f73f05a72535fd8c86ad4629175019037441a758b827c1ba47cc9a52ebd" + } + } + } + }, + "mck": { + "repository": "finos/morphir", + "tag": "v0.4.0-beta.11", + "sourceRevision": "9d4f4b4de0f9bd10549493bdd9c83f20709055f9", + "runners": { + "aarch64-apple-darwin": { + "filename": "morphir-0.4.0-beta.11-aarch64-apple-darwin.tgz", + "sha256": "1a1895c0b189b79c0cbb590c2b43d05c5ea9688bbc4b5cf8be6262cf57c19380" + }, + "x86_64-apple-darwin": { + "filename": "morphir-0.4.0-beta.11-x86_64-apple-darwin.tgz", + "sha256": "e93f826039bbaf95226c7e3cc7698920ae3968ab5c3263633fe0188d334dfcf1" + }, + "aarch64-unknown-linux-gnu": { + "filename": "morphir-0.4.0-beta.11-aarch64-unknown-linux-gnu.tgz", + "sha256": "141c7d75a5539e24f344a55c78b00a7cf22397b155f60f7c00787402660fc28f" + }, + "x86_64-unknown-linux-gnu": { + "filename": "morphir-0.4.0-beta.11-x86_64-unknown-linux-gnu.tgz", + "sha256": "01d0f96bbe73dadf06188d725b823d407c0bf79ef78869d57669744eed9fd437" + }, + "aarch64-pc-windows-msvc": { + "filename": "morphir-0.4.0-beta.11-aarch64-pc-windows-msvc.zip", + "sha256": "4ca0af7b9e2bc9f632ae49baca6baf615eb0171008d1a6e924a981e3a4a347c6" + }, + "x86_64-pc-windows-msvc": { + "filename": "morphir-0.4.0-beta.11-x86_64-pc-windows-msvc.zip", + "sha256": "da1a24c53802fcbb2750813b2fd865334b47b3410bdae9afdc64e5a59abb1cba" + } + }, + "kits": { + "guest-moonbit-bool": { + "path": "spec/mep/mck/guest-moonbit-bool.json", + "sha256": "59485eb156d50d3f4255c67561fb509e3a938aff1739d22a27099043b236a5d0" + }, + "guest-python-json": { + "path": "spec/mep/mck/guest-python-json.json", + "sha256": "9b4b5079ec36275b6ab5f11e7029e40959bc6577dcae41c4659d4151fab45a9d" + } + } + } +} diff --git a/apps/morphir/fixtures/sdk-guest/Cargo.lock b/apps/morphir/fixtures/sdk-guest/Cargo.lock new file mode 100644 index 000000000..107088c81 --- /dev/null +++ b/apps/morphir/fixtures/sdk-guest/Cargo.lock @@ -0,0 +1,1523 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a5b0a5ab8561c603f16c3b78c24a386b1f70e7fc1b1b1e8c15738828e4a176" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "compact_str" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "delegate" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "encoding_rs" +version = "0.8.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89253175e704ab869b04f9db224d38630e0211a53841a79181deae0889e169f5" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "extism-convert" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f765cafef8be05cf4518613ae2c6b1113e2a1fa630b632e7e8e53f6af8e6bae9" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bytemuck", + "extism-convert-macros", + "prost", + "rmp-serde", + "serde", + "serde_json", +] + +[[package]] +name = "extism-convert-macros" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd35d75555de7859923a276a03540ce90778c57e832f6d33359a6b465f4475a" +dependencies = [ + "manyhow", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "extism-manifest" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d8c1cfe930f60da0875e828261f63093f2379e1c2901b3302b5b3b6893a46a" +dependencies = [ + "base64 0.22.1", + "serde", + "serde_json", +] + +[[package]] +name = "extism-pdk" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352fcb5a66eb74145a1c4a01f2bd15d59c62c85be73aac8471880c65b26b798f" +dependencies = [ + "anyhow", + "base64 0.22.1", + "extism-convert", + "extism-manifest", + "extism-pdk-derive", + "serde", + "serde_json", +] + +[[package]] +name = "extism-pdk-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d086daea5fd844e3c5ac69ddfe36df4a9a43e7218cf7d1f888182b089b09806c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "globset" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98b0d85522dc0a824cfebbc9594ff4a9b289c568bea0370319dfbdf20ccdd2d" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "granit-parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0" +dependencies = [ + "arraydeque", + "smallvec", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ice_code" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6524844f553e8da5999f3000cf11d3f1ff926bb03fc087441c7b86dee4a7d48" + +[[package]] +name = "icu_collections" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ion-rs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285b4429808446f30da668d210beaeb7ab82defb6045e261c6cdfaeafe565552" +dependencies = [ + "arrayvec", + "base64 0.12.3", + "bumpalo", + "chrono", + "compact_str", + "delegate", + "generic-array", + "ice_code", + "memchr", + "num-bigint", + "num-integer", + "num-traits", + "phf", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "visibility", + "winnow 0.6.24", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "morphir-config" +version = "0.0.1" +source = "git+https://github.com/finos/morphir-rust?rev=de35f596a07deb8a3ba25b0f2988b217d111fecf#de35f596a07deb8a3ba25b0f2988b217d111fecf" +dependencies = [ + "secrecy", + "serde", + "serde-saphyr", + "serde_json", + "thiserror 2.0.21", + "toml", +] + +[[package]] +name = "morphir-extension-sdk" +version = "0.4.0" +source = "git+https://github.com/finos/morphir-rust?rev=de35f596a07deb8a3ba25b0f2988b217d111fecf#de35f596a07deb8a3ba25b0f2988b217d111fecf" +dependencies = [ + "base64 0.22.1", + "extism-pdk", + "ion-rs", + "morphir-json", + "morphir-workspace", + "percent-encoding", + "semver", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.21", + "url", +] + +[[package]] +name = "morphir-json" +version = "0.2.0" +source = "git+https://github.com/finos/morphir-rust?rev=de35f596a07deb8a3ba25b0f2988b217d111fecf#de35f596a07deb8a3ba25b0f2988b217d111fecf" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "morphir-sdk-fixture-guest" +version = "0.1.0" +dependencies = [ + "morphir-extension-sdk", + "serde_json", +] + +[[package]] +name = "morphir-workspace" +version = "0.2.0" +source = "git+https://github.com/finos/morphir-rust?rev=de35f596a07deb8a3ba25b0f2988b217d111fecf#de35f596a07deb8a3ba25b0f2988b217d111fecf" +dependencies = [ + "globset", + "morphir-config", + "semver", + "serde", + "serde_json", + "thiserror 2.0.21", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-saphyr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8" +dependencies = [ + "annotate-snippets", + "base64 0.23.1", + "encoding_rs_io", + "granit-parser", + "nohash-hasher", + "num-traits", + "serde_core", + "smallvec", + "zmij", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "1.1.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-width" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca718471876992059f251e33aafc334346ada08042b7661dd1fe313a9bced557" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" +dependencies = [ + "cfg-if", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/apps/morphir/fixtures/sdk-guest/Cargo.toml b/apps/morphir/fixtures/sdk-guest/Cargo.toml new file mode 100644 index 000000000..0904a0511 --- /dev/null +++ b/apps/morphir/fixtures/sdk-guest/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "morphir-sdk-fixture-guest" +version = "0.1.0" +edition = "2024" +rust-version = "1.98" +license = "Apache-2.0" +publish = false +description = "Conformance fixture guest for the Morphir CLI worker host, built with morphir-extension-sdk" + +# A standalone crate: no parent workspace. +[workspace] + +[lib] +crate-type = ["cdylib"] +path = "src/lib.rs" + +[dependencies] +# Pinned revision of finos/morphir-rust; see build-receipt.json. +morphir-extension-sdk = { git = "https://github.com/finos/morphir-rust", rev = "de35f596a07deb8a3ba25b0f2988b217d111fecf" } +serde_json = "1.0" + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +strip = true +panic = "abort" diff --git a/apps/morphir/fixtures/sdk-guest/README.md b/apps/morphir/fixtures/sdk-guest/README.md new file mode 100644 index 000000000..4e5dfa29e --- /dev/null +++ b/apps/morphir/fixtures/sdk-guest/README.md @@ -0,0 +1,46 @@ +# SDK conformance fixture guest + +This WASM guest shows that the CLI's worker host runs an extension from another +toolchain with no change made for it. It is built in Rust with +`morphir-extension-sdk` from finos/morphir-rust at revision +`de35f596a07deb8a3ba25b0f2988b217d111fecf`, for `wasm32-wasip1`. It is a test +fixture, not a published extension. + +The guest claims a language that no host knows, `morphir-fixture-flags`, with the +file extension `.flags`. A `.flags` document holds one Boolean constant per line: + +```text +# comment +ready = true +blockedNow = false +``` + +Compile writes one Morphir IR 3 module for each document. Generate (target +`morphir-fixture-flags`) writes the constants back as `.flags` documents, so the +generated files compile back to the same IR. + +The module imports the Extism kernel (`extism:host/env`), all four Morphir host +functions (`extism:host/user`: `morphir_get_workspace_info`, `morphir_cache_ir`, +`morphir_get_cached_ir`, `morphir_log`) and WASI preview 1 functions through the +Rust standard library. On each compile the guest uses them and reports the +answers in one `fixture.host` info diagnostic, for example: + +```json +{"cacheRoundTrip":true,"morphirLog":true,"wasiArguments":0,"wasiClock":true,"wasiEnvironment":0,"wasiRandomSeed":true,"workspaceRoot":true} +``` + +`build-receipt.json` records the toolchain, the SDK revision, the source digests, +the full import list and the SHA-256 of `morphir-sdk-fixture-guest.wasm`. To build +it again and compare: + +```sh +cd apps/morphir/fixtures/sdk-guest +cargo +1.98.1 build --release --locked --target wasm32-wasip1 +shasum -a 256 target/wasm32-wasip1/release/morphir_sdk_fixture_guest.wasm +``` + +`mise run test:extension-conformance` runs the guest through the native and npm +CLIs and checks its imports, claims, compile, generate and host function answers. +No shared MEP kit profile fits this guest: the shared kits qualify the MoonBit +Boolean library and the Python JSON library profiles, and both name their own +extension. A generic kit profile is follow-up bead `morphir-ukvn.5.64.36.15`. diff --git a/apps/morphir/fixtures/sdk-guest/build-receipt.json b/apps/morphir/fixtures/sdk-guest/build-receipt.json new file mode 100644 index 000000000..7f40a9e28 --- /dev/null +++ b/apps/morphir/fixtures/sdk-guest/build-receipt.json @@ -0,0 +1,56 @@ +{ + "profile": "morphir/fixture-build-receipt", + "contractVersion": "0.1.0", + "artifact": { + "path": "morphir-sdk-fixture-guest.wasm", + "sha256": "f5e1c9cd7a3472b6261c2c3ebfe2c39aeae14da5287dac6cd6ca87ce854901f1", + "bytes": 752806 + }, + "sdk": { + "crate": "morphir-extension-sdk", + "repository": "https://github.com/finos/morphir-rust", + "revision": "de35f596a07deb8a3ba25b0f2988b217d111fecf" + }, + "toolchain": { + "rustc": "1.98.1 (48a229cea 2026-09-01)", + "cargo": "1.98.1 (797e8a9bc 2026-08-05)", + "target": "wasm32-wasip1", + "host": "aarch64-apple-darwin" + }, + "command": "cargo +1.98.1 build --release --locked --target wasm32-wasip1", + "output": "target/wasm32-wasip1/release/morphir_sdk_fixture_guest.wasm", + "reproduced": "two clean builds in different target directories gave the same SHA-256", + "sources": { + "Cargo.toml": "9764baf299af8c097773b41fa97a1fc0d33faf2b12de100a973a288936309542", + "Cargo.lock": "2b6dac86519d502495dae34b83e642926e9788222a4e09d954097a8643ba6a64", + "src/lib.rs": "252691958e74cdf139ea45b9d20e3c797fa6110efe0e3091795f5f5521ef450d" + }, + "imports": [ + "extism:host/user.morphir_get_workspace_info", + "extism:host/env.length", + "extism:host/env.free", + "extism:host/user.morphir_cache_ir", + "extism:host/user.morphir_get_cached_ir", + "extism:host/env.get_log_level", + "extism:host/env.log_info", + "extism:host/user.morphir_log", + "wasi_snapshot_preview1.args_sizes_get", + "wasi_snapshot_preview1.args_get", + "extism:host/env.input_length", + "extism:host/env.input_load_u64", + "extism:host/env.input_load_u8", + "extism:host/env.error_set", + "extism:host/env.output_set", + "extism:host/env.alloc", + "extism:host/env.store_u64", + "extism:host/env.store_u8", + "extism:host/env.load_u64", + "extism:host/env.load_u8", + "wasi_snapshot_preview1.random_get", + "wasi_snapshot_preview1.environ_get", + "wasi_snapshot_preview1.environ_sizes_get", + "wasi_snapshot_preview1.clock_time_get", + "wasi_snapshot_preview1.fd_write", + "wasi_snapshot_preview1.proc_exit" + ] +} diff --git a/apps/morphir/fixtures/sdk-guest/morphir-sdk-fixture-guest.wasm b/apps/morphir/fixtures/sdk-guest/morphir-sdk-fixture-guest.wasm new file mode 100755 index 0000000000000000000000000000000000000000..5970f1295cee2b4987d539231324844271ec9371 GIT binary patch literal 752806 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%0DYsXa}EWz;<1NZlW~+N4&9m>y%GEPr+_l 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 z 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%0DPreIvm+$V~(q?}R|IEHe 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 ExtensionInfo { + ExtensionInfo { + id: "morphir-sdk-fixture".into(), + name: "Morphir SDK conformance fixture".into(), + version: "0.1.0".into(), + description: Some( + "Worker host conformance fixture built with morphir-extension-sdk".into(), + ), + types: vec![], + author: None, + homepage: None, + license: Some("Apache-2.0".into()), + min_sdk_version: None, + } + } + + fn capabilities() -> ExtensionCapabilities { + ExtensionCapabilities { + frontend: Some(FrontendCapability { + languages: vec![LanguageCapability { + id: LANGUAGE.into(), + file_extensions: vec![FILE_EXTENSION.into()], + }], + ir_versions: vec!["3".into()], + compile: true, + incremental: false, + fragments: false, + multi_document: true, + }), + backend: Some(BackendCapability { + targets: vec![TARGET.into()], + ir_versions: vec!["3".into()], + generate: true, + }), + ..Default::default() + } + } +} + +impl Frontend for FixtureGuest { + fn compile(&self, request: CompileRequest) -> morphir_extension_sdk::Result { + let mut diagnostics = vec![host_report()]; + if request.language_id != LANGUAGE { + diagnostics.push(error( + "fixture.language", + format!("this fixture compiles {LANGUAGE}, not {}", request.language_id), + )); + return Ok(failed(diagnostics)); + } + if request.options.ir_version != "3" && request.options.ir_version != "3.0.0" { + diagnostics.push(error( + "fixture.ir_version", + format!("this fixture writes IR 3, not {}", request.options.ir_version), + )); + return Ok(failed(diagnostics)); + } + let mut documents: Vec<_> = request.sources.documents.iter().collect(); + documents.sort_by(|left, right| left.uri.cmp(&right.uri)); + let mut modules = Vec::new(); + for document in documents { + match module(&document.uri, &document.text) { + Ok(module) => modules.push(module), + Err(message) => diagnostics.push(error("fixture.syntax", message)), + } + } + if diagnostics.len() > 1 { + return Ok(failed(diagnostics)); + } + let ir = json!({ + "formatVersion": 3, + "distribution": ["Library", path(&request.package.name), [], {"modules": modules}], + }); + Ok(CompileResult { + success: true, + ir_version: Some("3".into()), + ir: Some(ir), + diagnostics, + modules: vec![], + module_results: vec![], + context_digest: None, + }) + } + + fn supported_languages() -> Vec { + vec![LANGUAGE.into()] + } + + fn file_extensions() -> Vec { + vec![FILE_EXTENSION.into()] + } +} + +impl Backend for FixtureGuest { + fn generate(&self, request: GenerateRequest) -> morphir_extension_sdk::Result { + match artifacts(&request.ir) { + Ok(artifacts) => Ok(GenerateResult { + success: true, + artifacts, + diagnostics: vec![], + }), + Err(message) => Ok(GenerateResult { + success: false, + artifacts: vec![], + diagnostics: vec![error("fixture.unsupported_ir", message)], + }), + } + } + + fn target_languages() -> Vec { + vec![TARGET.into()] + } +} + +export_extension!(FixtureGuest, frontend, backend); + +/// The Boolean type reference of the Morphir SDK, in IR 3 JSON. +fn bool_type() -> Value { + json!(["Reference", {}, [[["morphir"], ["s", "d", "k"]], [["basics"]], ["bool"]], []]) +} + +/// A Morphir name: lower-case words, split at `_`, `-`, `.` and capitals. +fn name(text: &str) -> Value { + let mut words: Vec = Vec::new(); + let mut word = String::new(); + for character in text.chars() { + if character == '_' || character == '-' || character == '.' { + if !word.is_empty() { + words.push(std::mem::take(&mut word)); + } + } else if character.is_uppercase() { + if !word.is_empty() { + words.push(std::mem::take(&mut word)); + } + word.extend(character.to_lowercase()); + } else { + word.push(character); + } + } + if !word.is_empty() { + words.push(word); + } + json!(words) +} + +/// A Morphir path from a `/`-separated package name. +fn path(text: &str) -> Value { + Value::Array(text.split('/').filter(|part| !part.is_empty()).map(name).collect()) +} + +/// The module name of a document: its file name without `.flags`. +fn module_name(uri: &str) -> std::result::Result { + let file = uri.rsplit('/').next().unwrap_or(uri); + match file.strip_suffix(FILE_EXTENSION) { + Some(stem) if !stem.is_empty() => Ok(stem.to_string()), + _ => Err(format!("{uri}: a document name must end with {FILE_EXTENSION}")), + } +} + +fn module(uri: &str, text: &str) -> std::result::Result { + let module = module_name(uri)?; + let mut values = Vec::new(); + for (index, line) in text.lines().enumerate() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + let parse = || -> Option<(&str, bool)> { + let (key, value) = line.split_once('=')?; + let key = key.trim(); + let valid = !key.is_empty() + && key.chars().next()?.is_ascii_lowercase() + && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'); + let value = match value.trim() { + "true" => true, + "false" => false, + _ => return None, + }; + valid.then_some((key, value)) + }; + let Some((key, value)) = parse() else { + return Err(format!( + "{uri}:{}: expected `name = true` or `name = false`", + index + 1 + )); + }; + values.push(json!([name(key), {"access": "Public", "value": {"doc": "", "value": { + "inputTypes": [], + "outputType": bool_type(), + "body": ["Literal", bool_type(), ["BoolLiteral", value]], + }}}])); + } + Ok(json!([[name(&module)], {"access": "Public", "value": { + "types": [], "values": values, "doc": null, + }}])) +} + +/// Joins the words of a Morphir name with `_`, the form that compiles back +/// to the same name. +fn words(value: &Value) -> Option { + let words: Option> = value.as_array()?.iter().map(Value::as_str).collect(); + Some(words?.join("_")) +} + +fn artifacts(ir: &Value) -> std::result::Result, String> { + let unsupported = || "expected a Morphir IR 3 Library of Boolean constants".to_string(); + if ir["formatVersion"] != json!(3) || ir["distribution"][0] != json!("Library") { + return Err(unsupported()); + } + let modules = ir["distribution"][3]["modules"].as_array().ok_or_else(unsupported)?; + let mut artifacts = Vec::new(); + for module in modules { + let module_path = module[0].as_array().ok_or_else(unsupported)?; + let file: Option> = module_path.iter().map(words).collect(); + let file = file.ok_or_else(unsupported)?.join("/"); + let mut content = String::new(); + for value in module[1]["value"]["values"].as_array().ok_or_else(unsupported)? { + let key = words(&value[0]).ok_or_else(unsupported)?; + let body = &value[1]["value"]["value"]["body"]; + let literal = body[2][1].as_bool().filter(|_| body[0] == json!("Literal")); + let literal = literal.ok_or_else(unsupported)?; + content.push_str(&format!("{key} = {literal}\n")); + } + artifacts.push(Artifact { + path: format!("{file}{FILE_EXTENSION}"), + content, + binary: false, + }); + } + Ok(artifacts) +} + +fn error(code: &str, message: String) -> Diagnostic { + Diagnostic { + severity: DiagnosticSeverity::Error, + code: Some(code.into()), + message, + location: None, + related: vec![], + } +} + +fn failed(diagnostics: Vec) -> CompileResult { + CompileResult { + success: false, + ir_version: None, + ir: None, + diagnostics, + modules: vec![], + module_results: vec![], + context_digest: None, + } +} + +/// Uses the host functions and WASI, and reports what the host answered. +fn host_report() -> Diagnostic { + let report = observe_host(); + Diagnostic { + severity: DiagnosticSeverity::Info, + code: Some("fixture.host".into()), + message: report.to_string(), + location: None, + related: vec![], + } +} + +#[cfg(target_arch = "wasm32")] +fn observe_host() -> Value { + use morphir_extension_sdk::host; + let workspace = host::get_workspace_info(); + let key = "fixture/cache-probe"; + let probe = json!({"probe": [1, 2, 3]}); + host::cache_ir(key, &probe); + let cached = host::get_cached_ir(key); + host::log("info", "fixture guest: Extism kernel log"); + let logged = log::send("info", "fixture guest: morphir_log"); + // WASI preview 1: clock_time_get, random_get (the map's seed), + // args_sizes_get, environ_sizes_get and fd_write on stderr. + let clock = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .is_ok(); + let mut seeded = std::collections::HashMap::new(); + seeded.insert("seed", 1); + let arguments = std::env::args().count(); + let environment = std::env::vars().count(); + eprintln!("fixture guest: WASI stderr"); + json!({ + "workspaceRoot": workspace.as_ref().is_some_and(|info| !info.root.is_empty()), + "cacheRoundTrip": cached == Some(probe), + "morphirLog": logged, + "wasiClock": clock, + "wasiRandomSeed": seeded.len() == 1, + "wasiArguments": arguments, + "wasiEnvironment": environment, + }) +} + +#[cfg(not(target_arch = "wasm32"))] +fn observe_host() -> Value { + json!({"native": true}) +} + +/// `morphir_log`, the fourth Morphir host function. The SDK at the pinned +/// revision does not declare it, so the fixture declares it as a guest would. +#[cfg(target_arch = "wasm32")] +mod log { + use morphir_extension_sdk::extism_pdk::{self, *}; + + #[host_fn] + extern "ExtismHost" { + fn morphir_log(level: String, message: String); + } + + pub fn send(level: &str, message: &str) -> bool { + unsafe { morphir_log(level.to_string(), message.to_string()).is_ok() } + } +} diff --git a/apps/morphir/scripts/extension-qualification-consumer.py b/apps/morphir/scripts/extension-qualification-consumer.py new file mode 100644 index 000000000..422d84065 --- /dev/null +++ b/apps/morphir/scripts/extension-qualification-consumer.py @@ -0,0 +1,31 @@ +"""Consumer check of the Python that the Python extension generated. + +Usage: python3 -I extension-qualification-consumer.py + +The generated directory holds the package of the extism-python fixture +(`acme/models.py` and `acme/rules.py`). The check compiles every generated file, +imports the rules module and calls its functions. It needs Python 3.12 or +later, because the generated models use `type` statements. +""" +from __future__ import annotations + +import importlib +import json +import sys +from pathlib import Path + +assert sys.version_info >= (3, 12), f"Python 3.12 or later is required, not {sys.version}" +generated = Path(sys.argv[1]).resolve() +files = sorted(generated.rglob("*.py")) +assert files, f"{generated} holds no Python files" +for file in files: + compile(file.read_text(), str(file), "exec") +sys.path.insert(0, str(generated)) +rules = importlib.import_module("acme.rules") +assert rules.choose(True, 5, 7) == 5 +assert rules.choose(False, 5, 7) == 7 +assert rules.twice_identity(9) == 9 +assert rules.apply_twice(lambda value: value + 1, 1) == 3 +assert rules.large_value() == 9007199254740993 +assert rules.pair(1.0, 2.0) == (1.0, 2.0) +print(json.dumps({"python": sys.version.split()[0], "files": [f.relative_to(generated).as_posix() for f in files], "consumer": True})) diff --git a/apps/morphir/scripts/extension-qualification.ts b/apps/morphir/scripts/extension-qualification.ts new file mode 100644 index 000000000..adf41cab3 --- /dev/null +++ b/apps/morphir/scripts/extension-qualification.ts @@ -0,0 +1,741 @@ +// Qualification of published and fixture extensions on the CLI's worker host. +// +// node apps/morphir/scripts/extension-qualification.ts --suite --evidence +// --native --npm-entry +// [--python ] [--portable ] +// [--native-guest ] +// +// Suites: elm (task 7.1), python (7.2), moonbit-native and moonbit-portable (7.3), +// conformance (7.4). `scripts/extension-qualification.mbtx` builds the inputs and +// runs this file; run that script, not this file, from mise. +// +// Every downloaded file goes into a new, empty directory and is used only after +// its SHA-256 equals the pin in `apps/morphir/fixtures/qualification/published.json`. +// Each operation becomes one receipt row that names the extension, its version, +// the CLI, the platform, the operation, the result and the guest digest. The +// receipt is written to `/.json`; nothing is written to stdout. +// Progress goes to stderr. The exit status is 0 when every row passed or is a +// recorded known failure, and 1 otherwise. + +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { spawnSync, type SpawnSyncReturns } from 'node:child_process'; +import { + chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, + renameSync, rmSync, statSync, writeFileSync, +} from 'node:fs'; +import { delimiter, dirname, join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parseArgs } from 'node:util'; + +const repository = realpathSync(fileURLToPath(new URL('../../../', import.meta.url))); +const fixtures = join(repository, 'apps/morphir/fixtures'); +const published = JSON.parse(readFileSync(join(fixtures, 'qualification/published.json'), 'utf8')); + +/** A SHA-256 digest as 64 lower-case hexadecimal digits. */ +type Sha256 = string & { readonly __sha256: unique symbol }; +/** A release platform, for example `aarch64-apple-darwin`. */ +type Platform = string & { readonly __platform: unique symbol }; +type CliName = 'native' | 'npm'; +type RowResult = 'passed' | 'failed' | 'known-failure'; + +const sha256 = (path: string) => createHash('sha256').update(readFileSync(path)).digest('hex') as Sha256; +const log = (message: string) => process.stderr.write(`${message}\n`); + +/** The release platform of this machine. */ +export function hostPlatform(): Platform { + const arch = ({ arm64: 'aarch64', x64: 'x86_64' } as Record)[process.arch]; + assert.ok(arch, `No release platform for ${process.arch}`); + if (process.platform === 'darwin') return `${arch}-apple-darwin` as Platform; + if (process.platform === 'win32') return `${arch}-pc-windows-msvc` as Platform; + const report = process.report?.getReport?.() as { header?: { glibcVersionRuntime?: string } } | undefined; + return `${arch}-unknown-linux-${report?.header?.glibcVersionRuntime ? 'gnu' : 'musl'}` as Platform; +} + +// --------------------------------------------------------------------------- +// Downloads + +const downloads = join(repository, '.dev/extension-qualification/downloads'); + +/** + * Returns a verified local copy of one pinned file. A file already verified in + * the cache directory named by its digest is hashed again and reused. Otherwise + * the file is downloaded into a new, empty directory and moved into the cache + * only when its SHA-256 equals the pin. + */ +function fetchPinned(url: string, filename: string, expected: Sha256): string { + const cached = join(downloads, expected, filename); + if (existsSync(cached) && sha256(cached) === expected) return realpathSync(cached); + mkdirSync(downloads, { recursive: true }); + const fresh = mkdtempSync(join(downloads, 'download-')); + const target = join(fresh, filename); + log(`download ${url}`); + const curl = spawnSync('curl', ['--fail', '--silent', '--show-error', '--location', '--proto', '=https', + '--output', target, url], { stdio: ['ignore', 'inherit', 'inherit'] }); + assert.equal(curl.status, 0, `download failed: ${url}`); + const actual = sha256(target); + if (actual !== expected) { + rmSync(fresh, { recursive: true, force: true }); + assert.fail(`${filename}: SHA-256 ${actual} differs from the pin ${expected}`); + } + rmSync(join(downloads, expected), { recursive: true, force: true }); + renameSync(fresh, join(downloads, expected)); + return realpathSync(cached); +} + +const releaseUrl = (repo: string, tag: string, filename: string) => + `https://github.com/${repo}/releases/download/${encodeURIComponent(tag)}/${filename}`; + +function fetchRelease(section: { repository: string; tag: string }, asset: { filename: string; sha256: string }) { + return fetchPinned(releaseUrl(section.repository, section.tag, asset.filename), asset.filename, asset.sha256 as Sha256); +} + +/** The shared MCK runner of the pinned finos/morphir release, unpacked in a new directory. */ +function mckRunner(platform: Platform): { runner: string; archiveSha256: Sha256 } { + const asset = published.mck.runners[platform]; + assert.ok(asset, `No pinned MCK runner for ${platform}`); + const archive = fetchRelease(published.mck, asset); + const unpacked = join(dirname(archive), 'unpacked'); + if (!existsSync(unpacked)) { + const fresh = mkdtempSync(join(dirname(archive), 'unpack-')); + const tar = spawnSync('tar', ['-xf', archive, '-C', fresh], { stdio: ['ignore', 'inherit', 'inherit'] }); + assert.equal(tar.status, 0, `cannot unpack ${archive}`); + renameSync(fresh, unpacked); + } + const name = process.platform === 'win32' ? 'morphir.exe' : 'morphir'; + const found = findFile(unpacked, name); + assert.ok(found, `${archive} holds no ${name}`); + return { runner: found, archiveSha256: asset.sha256 }; +} + +function findFile(root: string, name: string): string | undefined { + for (const entry of readdirSync(root, { withFileTypes: true })) { + const path = join(root, entry.name); + if (entry.isFile() && entry.name === name) return path; + if (entry.isDirectory()) { + const found = findFile(path, name); + if (found) return found; + } + } + return undefined; +} + +/** A shared MEP kit at the pinned finos/morphir revision. */ +function kit(name: 'guest-moonbit-bool' | 'guest-python-json'): { path: string; sha256: Sha256; value: any } { + const pin = published.mck.kits[name]; + const url = `https://raw.githubusercontent.com/${published.mck.repository}/${published.mck.sourceRevision}/${pin.path}`; + const path = fetchPinned(url, `${name}.json`, pin.sha256); + return { path, sha256: pin.sha256, value: JSON.parse(readFileSync(path, 'utf8')) }; +} + +// --------------------------------------------------------------------------- +// The two CLIs + +interface Cli { + name: CliName; + command: string; + prefix: string[]; + /** The environment of every run: a minimal PATH, with Node only for the npm CLI. */ + env: NodeJS.ProcessEnv; + build: { commit: string; dirty: boolean; programSha256: Sha256; entrySha256?: Sha256; node?: string }; +} + +function baseEnvironment(home: string, path: string[]): NodeJS.ProcessEnv { + if (process.platform === 'win32') { + const root = process.env.SystemRoot ?? 'C:\\Windows'; + return { SystemRoot: root, PATH: [...path, join(root, 'System32')].join(delimiter), TEMP: home, TMP: home, USERPROFILE: home }; + } + return { PATH: [...path, '/usr/bin', '/bin'].join(delimiter), HOME: home }; +} + +/** Whether `node` can be found on the PATH of an environment. */ +function nodeVisible(env: NodeJS.ProcessEnv): boolean { + const probe = process.platform === 'win32' + ? spawnSync('where.exe', ['node'], { env, encoding: 'utf8' }) + : spawnSync('/bin/sh', ['-c', 'command -v node'], { env, encoding: 'utf8' }); + return probe.status === 0; +} + +function gitBuild() { + const git = (args: string[]) => spawnSync('git', args, { cwd: repository, encoding: 'utf8' }).stdout.trim(); + return { commit: git(['rev-parse', 'HEAD']), dirty: git(['status', '--porcelain']) !== '' }; +} + +function clis(native: string, npmEntry: string, home: string): Cli[] { + const build = gitBuild(); + return [ + { name: 'native', command: native, prefix: [], env: baseEnvironment(home, []), + build: { ...build, programSha256: sha256(native) } }, + { name: 'npm', command: process.execPath, prefix: [npmEntry], env: baseEnvironment(home, [dirname(process.execPath)]), + build: { ...build, programSha256: sha256(process.execPath), entrySha256: sha256(npmEntry), node: process.version } }, + ]; +} + +interface Run { status: number | null; stdout: string; stderr: string; report?: any } + +function run(cli: Cli, args: string[], cwd: string, timeout = 240000): Run { + const child: SpawnSyncReturns = spawnSync(cli.command, [...cli.prefix, ...args], + { cwd, env: cli.env, encoding: 'utf8', timeout, killSignal: 'SIGKILL', maxBuffer: 64 * 1024 * 1024 }); + const result: Run = { status: child.status, stdout: child.stdout ?? '', stderr: child.stderr ?? '' }; + if (args.includes('--json')) { + try { result.report = JSON.parse(result.stdout); } catch { /* the assertion below names it */ } + assert.ok(result.report, `${cli.name} ${args.join(' ')}: stdout is not one JSON document:\n${result.stdout}\n${result.stderr}`); + } + return result; +} + +/** Runs one command in JSON mode and checks its exit status and report. */ +function command(cli: Cli, args: string[], cwd: string, expected: number): any { + const result = run(cli, [...args, '--json'], cwd); + const tail = `${result.stdout}\n${result.stderr.slice(-4000)}`; + assert.equal(result.status, expected, `${cli.name} ${args.join(' ')} exited ${result.status}:\n${tail}`); + assert.equal(result.report.profile, 'morphir/command-result', tail); + assert.equal(result.report.contractVersion, '0.1.0', tail); + assert.equal(result.report.exitCode, expected, tail); + assert.equal(result.report.state, expected === 0 ? 'succeeded' : 'failed', tail); + return result.report; +} + +// --------------------------------------------------------------------------- +// Receipts + +interface Row { + extension: string; + version: string; + cli: CliName | 'mck'; + platform: Platform; + operation: string; + result: RowResult; + guestSha256: Sha256; + descriptorSha256?: Sha256; + outputs?: { path: string; bytes: number; sha256: Sha256 }[]; + detail?: string; + finding?: string; +} + +/** + * A failure that is recorded and explained, not hidden. The row is + * `known-failure` while the operation fails with a message that matches; an + * operation that passes instead fails the row, so the expectation is removed. + */ +interface KnownFailure { match: RegExp; finding: string } + +class Ledger { + readonly rows: Row[] = []; + readonly platform: Platform; + constructor(platform: Platform) { this.platform = platform; } + + step(base: Omit, body: () => Partial | void, known?: KnownFailure): boolean { + const row: Row = { ...base, platform: this.platform, result: 'passed' }; + try { + Object.assign(row, body() ?? {}); + if (known) { + row.result = 'failed'; + row.detail = `passed, but it is recorded as a known failure (${known.finding}); remove the expectation`; + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + row.detail = message.slice(0, 4000); + if (known && known.match.test(message)) { + row.result = 'known-failure'; + row.finding = known.finding; + } else row.result = 'failed'; + } + this.rows.push(row); + log(`${row.result.padEnd(13)} ${row.extension} ${row.version} ${row.cli} ${row.operation}${row.result === 'passed' ? '' : `: ${row.finding ?? row.detail?.split('\n')[0]}`}`); + return row.result === 'passed'; + } +} + +function outputs(directory: string, files: { path: string; bytes: number; sha256: string }[]) { + for (const file of files) assert.equal(sha256(join(directory, file.path)), file.sha256, `${file.path} digest`); + return files.map(file => ({ path: file.path, bytes: file.bytes, sha256: file.sha256 as Sha256 })); +} + +function treeDigests(root: string): Record { + const result: Record = {}; + const walk = (directory: string) => { + for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) { + const path = join(directory, entry.name); + if (entry.isDirectory()) walk(path); + else result[relative(root, path).split('\\').join('/')] = sha256(path); + } + }; + walk(root); + return result; +} + +function writePin(path: string, pin: Record) { + writeFileSync(path, `${JSON.stringify(pin, null, 2)}\n`); + return path; +} + +function processPin(artifact: string, platform: Platform, origin: string, descriptor?: string) { + return { + contractVersion: '0.2.0', + artifact: { path: artifact, sha256: sha256(artifact) }, + runtime: { kind: 'process', platform, args: [] }, + ...(descriptor ? { descriptor: { path: descriptor, sha256: sha256(descriptor) } } : {}), + origin, + }; +} + +function wasmPin(artifact: string, abi: string, origin: string, descriptor?: string) { + return { + contractVersion: '0.2.0', + artifact: { path: artifact, sha256: sha256(artifact) }, + runtime: { kind: 'wasm', abi }, + ...(descriptor ? { descriptor: { path: descriptor, sha256: sha256(descriptor) } } : {}), + origin, + }; +} + +/** Checks the describe report against the expected identity and worker kind. */ +function described(report: any, id: string, version: string, kind: 'process' | 'wasm') { + assert.equal(report.command, 'extension.describe'); + assert.equal(report.result.worker.kind, kind); + assert.deepEqual(report.result.extension, { id, version }); + return report.result; +} + +// --------------------------------------------------------------------------- +// Suites + +interface Context { + ledger: Ledger; + platform: Platform; + clis: Cli[]; + scratch: string; + python?: string; + portable?: string; + nativeGuest?: string; + notes: Record; +} + +/** Elm extension v0.4.1: published native executables as process workers (task 7.1). */ +function elmSuite(context: Context) { + const { ledger, platform } = context; + const elm = published.elm; + const asset = elm.executables[platform]; + assert.ok(asset, `The Elm extension ${elm.version} has no executable for ${platform}`); + const executable = fetchRelease(elm, asset); + if (process.platform !== 'win32') chmodSync(executable, 0o755); + const descriptor = fetchRelease(elm, elm.descriptor); + const pin = writePin(join(context.scratch, 'elm-pin.json'), + processPin(executable, platform, `${elm.repository} ${elm.tag}`, descriptor)); + const base = { extension: 'morphir-elm', version: elm.version, guestSha256: asset.sha256 as Sha256, descriptorSha256: elm.descriptor.sha256 as Sha256 }; + const compiled: Record> = {}; + for (const cli of context.clis) { + const directory = mkdtempSync(join(context.scratch, `elm-${cli.name}-`)); + ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-elm', elm.version, 'process'); + assert.equal(result.worker.descriptorSha256, elm.descriptor.sha256); + assert.deepEqual(result.claims.protocolVersions, ['0.1']); + return { detail: `claims ${JSON.stringify(result.claims.languages)}` }; + }); + if (cli.name === 'native') { + ledger.step({ ...base, cli: cli.name, operation: 'environment-without-node' }, () => { + assert.equal(nodeVisible(cli.env), false, `node is on the native run's PATH ${cli.env.PATH}`); + return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; + }); + } + ledger.step({ ...base, cli: cli.name, operation: 'compile' }, () => { + const report = command(cli, ['extension', 'compile', join(fixtures, 'elm-extension/v1/src'), '--package', 'acme/rules', + '--output', 'model', '--extension-pin', pin], directory, 0); + assert.equal(report.result.protocolVersion, '0.1'); + const ir = JSON.parse(readFileSync(join(directory, 'model/morphir-ir.json'), 'utf8')); + assert.equal(ir.formatVersion, 3); + compiled[cli.name] = treeDigests(join(directory, 'model')); + return { outputs: outputs(join(directory, 'model'), report.result.output.files) }; + }, { + match: /Invalid morphir\.frontend\.compile parameters/, + finding: 'Elm 0.4.1 requires package.exposedModules (cli2/mep/protocol.ts); the MEP Smithy model makes it optional and the host, which conforms, does not send it; follow-up bead morphir-ukvn.5.64.36.16 (finos/morphir-elm)', + }); + } + if (compiled.native && compiled.npm) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => { + assert.deepEqual(compiled.npm, compiled.native); + }); + } +} + +/** Python extension 0.4.1 and 0.4.2: extism-kernel-v1 WASM workers (task 7.2). */ +function pythonSuite(context: Context) { + const { ledger } = context; + const python = context.python; + for (const version of ['0.4.1', '0.4.2']) { + const release = published.python.releases[version]; + const section = { repository: published.python.repository, tag: release.tag }; + const module = fetchRelease(section, release.module); + const descriptor = fetchRelease(section, release.descriptor); + const pin = writePin(join(context.scratch, `python-${version}-pin.json`), + wasmPin(module, 'extism-kernel-v1', `${published.python.repository} ${release.tag}`, descriptor)); + const base = { extension: 'morphir-python', version, guestSha256: release.module.sha256 as Sha256, descriptorSha256: release.descriptor.sha256 as Sha256 }; + const results: Record>> = {}; + for (const cli of context.clis) { + const directory = mkdtempSync(join(context.scratch, `python-${version}-${cli.name}-`)); + cpSync(join(fixtures, 'extism-python/v1/acme'), join(directory, 'src/acme'), { recursive: true }); + cpSync(join(fixtures, 'extism-python/v1/single'), join(directory, 'single'), { recursive: true }); + results[cli.name] = {}; + ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-python', version, 'wasm'); + assert.equal(result.worker.runtime.abi, 'extism-kernel-v1'); + assert.equal(result.worker.descriptorSha256, release.descriptor.sha256); + return { detail: `multiDocument ${result.claims.frontendFlags?.multiDocument ?? false}` }; + }); + if (version === '0.4.1') { + for (const ir of ['3', '4']) { + ledger.step({ ...base, cli: cli.name, operation: `compile-single-document-ir${ir}` }, () => { + const report = command(cli, ['extension', 'compile', 'single/rules.py', '--package', 'acme/rules', '--ir-version', ir, + '--output', `single-v${ir}`, '--extension-pin', pin], directory, 0); + assert.equal(JSON.parse(readFileSync(join(directory, `single-v${ir}/morphir-ir.json`), 'utf8')).formatVersion, Number(ir)); + results[cli.name][`single-v${ir}`] = treeDigests(join(directory, `single-v${ir}`)); + return { outputs: outputs(join(directory, `single-v${ir}`), report.result.output.files) }; + }); + } + ledger.step({ ...base, cli: cli.name, operation: 'refuse-multi-document' }, () => { + const report = command(cli, ['extension', 'compile', 'src', '--package', 'acme/rules', '--ir-version', '3', + '--output', 'multi', '--extension-pin', pin], directory, 2); + assert.equal(report.error.code, 'extension.capability_unavailable'); + assert.match(report.error.message, /multiDocument/); + assert.equal(existsSync(join(directory, 'multi')), false); + assert.equal(existsSync(join(directory, 'multi.morphir-partial')), false); + return { detail: report.error.message }; + }); + continue; + } + for (const ir of ['3', '4']) { + const model = `model-v${ir}`, generated = `generated-v${ir}`, again = `again-v${ir}`; + const compiled = ledger.step({ ...base, cli: cli.name, operation: `compile-ir${ir}` }, () => { + const report = command(cli, ['extension', 'compile', 'src', '--package', 'acme/rules', '--ir-version', ir, + '--output', model, '--extension-pin', pin], directory, 0); + assert.equal(report.result.protocolVersion, '0.1'); + assert.equal(JSON.parse(readFileSync(join(directory, model, 'morphir-ir.json'), 'utf8')).formatVersion, Number(ir)); + results[cli.name][model] = treeDigests(join(directory, model)); + return { outputs: outputs(join(directory, model), report.result.output.files) }; + }); + if (!compiled) continue; + const generatedOk = ledger.step({ ...base, cli: cli.name, operation: `generate-ir${ir}` }, () => { + // MEP 0.1 generate takes the IR version from formatVersion and sends no options.irVersion. + const report = command(cli, ['extension', 'generate', join(model, 'morphir-ir.json'), '--output', generated, + '--extension-pin', pin], directory, 0); + results[cli.name][generated] = treeDigests(join(directory, generated)); + return { outputs: outputs(join(directory, generated), report.result.output.files) }; + }); + if (!generatedOk) continue; + ledger.step({ ...base, cli: cli.name, operation: `consumer-ir${ir}` }, () => { + assert.ok(python, 'no Python 3.12 or later for the consumer check; pass --python'); + const check = spawnSync(python, ['-I', join(repository, 'apps/morphir/scripts/extension-qualification-consumer.py'), + join(directory, generated)], { encoding: 'utf8', timeout: 60000 }); + assert.equal(check.status, 0, `${check.stdout}\n${check.stderr}`); + return { detail: check.stdout.trim() }; + }); + ledger.step({ ...base, cli: cli.name, operation: `recompile-generated-ir${ir}` }, () => { + command(cli, ['extension', 'compile', generated, '--package', 'acme/rules', '--ir-version', ir, + '--output', again, '--extension-pin', pin], directory, 0); + assert.deepEqual(JSON.parse(readFileSync(join(directory, again, 'morphir-ir.json'), 'utf8')), + JSON.parse(readFileSync(join(directory, model, 'morphir-ir.json'), 'utf8'))); + }); + ledger.step({ ...base, cli: cli.name, operation: `refuse-ir-version-mismatch-ir${ir}` }, () => { + const report = command(cli, ['extension', 'generate', join(model, 'morphir-ir.json'), '--ir-version', ir === '3' ? '4' : '3', + '--output', `mismatch-v${ir}`, '--extension-pin', pin], directory, 2); + assert.equal(report.error.code, 'extension.ir_version_mismatch'); + assert.equal(existsSync(join(directory, `mismatch-v${ir}`)), false); + }); + } + } + if (results.native && results.npm) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => { + assert.deepEqual(results.npm, results.native); + return { detail: `${Object.keys(results.native).length} output directories` }; + }); + } + } +} + +/** The MoonBit guest, native (process) or portable (morphir-bytes-v1), with the shared kit (task 7.3). */ +function moonbitSuite(context: Context, flavour: 'native' | 'portable') { + const { ledger, platform } = context; + const shared = kit('guest-moonbit-bool'); + const artifact = flavour === 'native' ? context.nativeGuest : context.portable && join(context.portable, 'model.wasm'); + assert.ok(artifact && existsSync(artifact), `No MoonBit ${flavour} guest; pass --${flavour === 'native' ? 'native-guest' : 'portable'}`); + const guestSha256 = sha256(artifact); + const pin = writePin(join(context.scratch, `moonbit-${flavour}-pin.json`), flavour === 'native' + ? processPin(artifact, platform, 'finos/morphir-moonbit apps/morphir-extension (local build)') + : wasmPin(artifact, 'morphir-bytes-v1', 'finos/morphir-moonbit mise run package:mep-extension (local build)')); + const base = { extension: 'morphir-moonbit', version: '0.1.0', guestSha256 }; + const source = shared.value.source; + const document = source.sources.documents[0]; + const results: Record>> = {}; + for (const cli of context.clis) { + const directory = mkdtempSync(join(context.scratch, `moonbit-${flavour}-${cli.name}-`)); + mkdirSync(join(directory, 'src')); + writeFileSync(join(directory, 'src/main.mbt'), document.text); + writeFileSync(join(directory, 'json-envelope.json'), JSON.stringify(jsonEnvelope(shared.value.plainIr))); + results[cli.name] = {}; + ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-moonbit', '0.1.0', flavour === 'native' ? 'process' : 'wasm'); + if (flavour === 'portable') assert.equal(result.worker.runtime.abi, 'morphir-bytes-v1'); + }); + if (cli.name === 'native' && flavour === 'native') { + ledger.step({ ...base, cli: cli.name, operation: 'environment-without-node' }, () => { + assert.equal(nodeVisible(cli.env), false); + return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; + }); + } + const compiled = ledger.step({ ...base, cli: cli.name, operation: 'compile-kit-source' }, () => { + const report = command(cli, ['extension', 'compile', 'src/main.mbt', '--package', source.package.name, '--output', 'model', + '--extension-pin', pin], directory, 0); + assert.equal(report.result.protocolVersion, '0.2.0-draft.1'); + assert.equal(report.result.negotiated.tuple.codec, 'ion-binary'); + results[cli.name].model = treeDigests(join(directory, 'model')); + return { outputs: outputs(join(directory, 'model'), report.result.output.files) }; + }); + if (compiled) { + const generated = ledger.step({ ...base, cli: cli.name, operation: 'generate' }, () => { + const report = command(cli, ['extension', 'generate', 'model/model-envelope.json', '--output', 'generated', + '--extension-pin', pin], directory, 0); + assert.deepEqual(readFileSync(join(directory, 'generated/mep/source-model.ionb')), readFileSync(join(directory, 'model/model.ionb'))); + results[cli.name].generated = treeDigests(join(directory, 'generated')); + return { outputs: outputs(join(directory, 'generated'), report.result.output.files) }; + }); + if (generated) { + ledger.step({ ...base, cli: cli.name, operation: 'kit-consumer' }, () => { + const stdout = moonbitConsumer(directory, join(directory, 'generated'), shared.value); + return { detail: stdout.trim() }; + }); + } + } + ledger.step({ ...base, cli: cli.name, operation: 'generate-kit-plain-ir' }, () => { + const report = command(cli, ['extension', 'generate', 'json-envelope.json', '--output', 'json-generated', + '--extension-pin', pin], directory, 0); + for (const [name, text] of Object.entries(shared.value.expectedJsonArtifacts as Record)) { + assert.equal(readFileSync(join(directory, 'json-generated', name), 'utf8'), text, `${name} differs from the kit`); + } + return { outputs: outputs(join(directory, 'json-generated'), report.result.output.files) }; + }); + } + if (results.native && results.npm) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(results.npm, results.native)); + } + mckGuestRun(context, flavour, artifact, shared, base); +} + +/** A Morphir JSON model envelope around the kit's independent plain IR. */ +function jsonEnvelope(plainIr: unknown) { + const bytes = Buffer.from(JSON.stringify(plainIr)); + const digest = createHash('sha256').update(bytes).digest('hex'); + return { + envelopeVersion: '0.1.0-draft.1', modelId: 'urn:morphir:mck:bool', revision: `sha256:${digest}`, irVersion: '4.0.0', + profile: 'morphir-ir', + payload: { codec: 'morphir-json', layout: 'single-document', decodedByteLength: bytes.length, sha256: digest, content: bytes.toString('base64') }, + }; +} + +/** Builds the generated library with the kit's consumer source and checks the kit's expected stdout. */ +function moonbitConsumer(directory: string, generated: string, kitValue: any): string { + const consumer = join(directory, 'consumer'); + mkdirSync(consumer); + for (const [name, text] of Object.entries(kitValue.consumer.files as Record)) writeFileSync(join(consumer, name), text); + writeFileSync(join(directory, 'moon.work'), `members = ${JSON.stringify([generated, consumer, join(repository, 'pkgs/morphir-sdk')])}\n`); + const moon = spawnSync('moon', ['-C', directory, 'run', '--target', 'native', 'consumer'], { encoding: 'utf8', timeout: 600000 }); + assert.equal(moon.status, 0, moon.stderr.slice(-4000)); + assert.equal(moon.stdout, kitValue.consumer.expectedStdout); + return moon.stdout; +} + +/** + * Runs the shared MEP kit against the guest. The native guest is its own stdio + * host. The portable guest is served by the native CLI's internal worker mode, + * `morphir extension worker`, with the CLI's own build identity. + */ +function mckGuestRun(context: Context, flavour: 'native' | 'portable', artifact: string, + shared: { path: string; sha256: Sha256 }, base: { extension: string; version: string; guestSha256: Sha256 }) { + const { ledger, platform } = context; + const native = context.clis.find(cli => cli.name === 'native')!; + const evidence = mkdtempSync(join(context.scratch, `mck-${flavour}-`)); + ledger.step({ ...base, cli: 'mck', operation: `shared-kit-${flavour === 'native' ? 'direct' : 'native-cli-worker-mode'}` }, () => { + const { runner, archiveSha256 } = mckRunner(platform); + const report = join(evidence, 'report.json'); + const args = ['--no-banner', 'mck', 'mep', 'guest-run', '--kit', shared.path, '--expect-kit-sha256', shared.sha256, '--report', report]; + if (flavour === 'native') args.push('--host', artifact, '--identity-file', artifact); + else { + const help = run(native, ['extension', 'worker', '--help', '--json'], evidence); + const identity = JSON.parse(help.stdout).result.buildIdentity; + assert.match(identity, /^[0-9a-f]{64}$/); + args.push('--host', native.command); + for (const value of ['extension', 'worker', '--abi', 'morphir-bytes-v1', '--module', artifact, '--build-identity', identity, + '--request-bytes', String(4 * 1024 * 1024), '--response-bytes', String(16 * 1024 * 1024)]) args.push('--host-arg', value); + args.push('--identity-file', native.command, '--identity-file', artifact); + } + const environment = process.platform === 'win32' ? { ...native.env } : { PATH: '', MOON_HOME: '', HOME: evidence }; + const guestRun = spawnSync(runner, args, { cwd: evidence, env: environment, encoding: 'utf8', timeout: 600000 }); + const observed = existsSync(report) ? JSON.parse(readFileSync(report, 'utf8')) : undefined; + const cases = observed?.records?.map((record: { caseId: string }) => record.caseId) ?? []; + const detail = `runner ${published.mck.tag} archive ${archiveSha256}; kit ${shared.sha256}; ${cases.length} cases`; + assert.equal(guestRun.status, 0, `${detail}; ${observed?.error ?? guestRun.stderr.slice(-2000)}; cases ${cases.join(', ')}`); + const recheck = spawnSync(runner, ['--no-banner', 'mck', 'mep', 'report-check', '--kit', shared.path, report], + { cwd: evidence, env: environment, encoding: 'utf8', timeout: 600000 }); + assert.equal(recheck.status, 0, recheck.stderr.slice(-2000)); + assert.equal(observed.qualified, true); + return { detail, outputs: [{ path: 'report.json', bytes: statSync(report).size, sha256: sha256(report) }] }; + }, { + match: /moonbit_frontend\.unsupported_profile|compile result is not a clean successful main module|frontend source profile changed/, + finding: 'the shared kit asks for frontend profile moonbit-model-bool-library-v1, which the MoonBit guest retired in #181, so case compile-ion fails; follow-up bead morphir-ukvn.5.64.36.17 (finos/morphir kit)', + }); +} + +/** The SDK fixture guest: local conformance check, no shared kit profile fits it (task 7.4). */ +function conformanceSuite(context: Context) { + const { ledger } = context; + const root = join(fixtures, 'sdk-guest'); + const receipt = JSON.parse(readFileSync(join(root, 'build-receipt.json'), 'utf8')); + const artifact = join(root, receipt.artifact.path); + const base = { extension: 'morphir-sdk-fixture', version: '0.1.0', guestSha256: sha256(artifact) }; + context.notes.sharedKit = 'No shared MEP kit profile fits this guest: the shared kits qualify the MoonBit Boolean library and Python JSON library profiles, and each names its own extension. A generic kit profile is follow-up bead morphir-ukvn.5.64.36.15.'; + context.notes.hostChange = 'None: the guest runs on the worker host as it is on main.'; + ledger.step({ ...base, cli: 'native', operation: 'build-receipt' }, () => { + assert.equal(base.guestSha256, receipt.artifact.sha256, 'the stored module differs from its build receipt'); + for (const [path, digest] of Object.entries(receipt.sources as Record)) { + assert.equal(sha256(join(root, path)), digest, `${path} differs from the build receipt`); + } + return { detail: `${receipt.sdk.crate} ${receipt.sdk.revision}; ${receipt.toolchain.rustc}; ${receipt.toolchain.target}` }; + }); + ledger.step({ ...base, cli: 'native', operation: 'imports' }, () => { + const imports = wasmImports(readFileSync(artifact)); + assert.deepEqual(imports, receipt.imports); + const modules = [...new Set(imports.map(name => name.slice(0, name.lastIndexOf('.'))))].sort(); + assert.deepEqual(modules, ['extism:host/env', 'extism:host/user', 'wasi_snapshot_preview1']); + for (const name of ['morphir_get_workspace_info', 'morphir_cache_ir', 'morphir_get_cached_ir', 'morphir_log']) { + assert.ok(imports.includes(`extism:host/user.${name}`), name); + } + return { detail: `${imports.filter(name => name.startsWith('wasi_')).length} WASI and 4 Morphir host function imports` }; + }); + const pin = writePin(join(context.scratch, 'sdk-fixture-pin.json'), + wasmPin(artifact, 'extism-kernel-v1', 'finos/morphir-moonbit apps/morphir/fixtures/sdk-guest')); + const results: Record>> = {}; + for (const cli of context.clis) { + const directory = mkdtempSync(join(context.scratch, `sdk-${cli.name}-`)); + mkdirSync(join(directory, 'src')); + writeFileSync(join(directory, 'src/rules.flags'), '# fixture\nready = true\nblockedNow = false\n'); + writeFileSync(join(directory, 'src/extra.flags'), 'enabled = true\n'); + writeFileSync(join(directory, 'src/ignored.txt'), 'not a claimed source file\n'); + results[cli.name] = {}; + ledger.step({ ...base, cli: cli.name, operation: 'describe-classify' }, () => { + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-sdk-fixture', '0.1.0', 'wasm'); + assert.equal(result.worker.runtime.abi, 'extism-kernel-v1'); + assert.deepEqual(result.claims.languages, [{ id: 'morphir-fixture-flags', fileExtensions: ['.flags'] }]); + return { detail: `operations ${result.claims.operations.join(', ')}` }; + }); + ledger.step({ ...base, cli: cli.name, operation: 'compile-claimed-sources' }, () => { + const report = command(cli, ['extension', 'compile', 'src', '--package', 'acme/flags', '--output', 'model', + '--extension-pin', pin], directory, 0); + const host = report.diagnostics.find((d: { code?: string }) => d.code === 'fixture.host'); + assert.ok(host, 'the guest reported no host observation'); + assert.deepEqual(JSON.parse(host.message), { + cacheRoundTrip: true, morphirLog: true, wasiArguments: 0, wasiClock: true, wasiEnvironment: 0, wasiRandomSeed: true, workspaceRoot: true, + }); + const ir = JSON.parse(readFileSync(join(directory, 'model/morphir-ir.json'), 'utf8')); + const modules = ir.distribution[3].modules.map((module: any) => module[0]); + assert.deepEqual(modules, [[['extra']], [['rules']]], 'the .txt file is not a claimed source'); + results[cli.name].model = treeDigests(join(directory, 'model')); + return { detail: host.message, outputs: outputs(join(directory, 'model'), report.result.output.files) }; + }); + ledger.step({ ...base, cli: cli.name, operation: 'generate-and-recompile' }, () => { + const report = command(cli, ['extension', 'generate', 'model/morphir-ir.json', '--output', 'generated', + '--extension-pin', pin], directory, 0); + assert.equal(readFileSync(join(directory, 'generated/rules.flags'), 'utf8'), 'ready = true\nblocked_now = false\n'); + command(cli, ['extension', 'compile', 'generated', '--package', 'acme/flags', '--output', 'again', '--extension-pin', pin], directory, 0); + assert.deepEqual(readFileSync(join(directory, 'again/morphir-ir.json')), readFileSync(join(directory, 'model/morphir-ir.json'))); + results[cli.name].generated = treeDigests(join(directory, 'generated')); + return { outputs: outputs(join(directory, 'generated'), report.result.output.files) }; + }); + ledger.step({ ...base, cli: cli.name, operation: 'guest-diagnostics' }, () => { + writeFileSync(join(directory, 'broken.flags'), 'ready = maybe\n'); + const report = command(cli, ['extension', 'compile', 'broken.flags', '--package', 'acme/flags', '--output', 'broken', + '--extension-pin', pin], directory, 1); + assert.equal(report.error.code, 'extension.operation_unsuccessful'); + assert.ok(report.diagnostics.some((d: { code?: string }) => d.code === 'fixture.syntax')); + assert.equal(existsSync(join(directory, 'broken')), false); + }); + } + if (results.native && results.npm) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(results.npm, results.native)); + } +} + +/** The names of the function imports of a WASM module, `module.name`, in order. */ +export function wasmImports(bytes: Buffer): string[] { + assert.deepEqual([...bytes.subarray(0, 4)], [0, 0x61, 0x73, 0x6d], 'not a WASM module'); + let offset = 8; + const leb = () => { + let result = 0, shift = 0, byte = 0; + do { byte = bytes[offset++]; result += (byte & 0x7f) * 2 ** shift; shift += 7; } while (byte & 0x80); + return result; + }; + const text = () => { const length = leb(); const value = bytes.subarray(offset, offset + length).toString('utf8'); offset += length; return value; }; + const names: string[] = []; + while (offset < bytes.length) { + const id = bytes[offset++]; + const size = leb(); + const end = offset + size; + if (id === 2) { + for (let count = leb(); count > 0; count--) { + const module = text(), name = text(); + assert.equal(bytes[offset++], 0, `${module}.${name} is not a function import`); + leb(); + names.push(`${module}.${name}`); + } + } + offset = end; + } + return names; +} + +// --------------------------------------------------------------------------- + +const suites = { + elm: elmSuite, python: pythonSuite, conformance: conformanceSuite, + 'moonbit-native': (context: Context) => moonbitSuite(context, 'native'), + 'moonbit-portable': (context: Context) => moonbitSuite(context, 'portable'), +} as const; +type SuiteName = keyof typeof suites; + +export function main(args: string[]) { + const { values } = parseArgs({ + args, strict: true, allowPositionals: false, + options: Object.fromEntries(['suite', 'evidence', 'native', 'npm-entry', 'python', 'portable', 'native-guest'] + .map(name => [name, { type: 'string' as const }])), + }); + const suite = values.suite as SuiteName; + assert.ok(suite in suites, `--suite must be one of ${Object.keys(suites).join(', ')}`); + const { evidence, native, 'npm-entry': npmEntry } = values; + assert.ok(evidence && native && npmEntry, '--evidence, --native and --npm-entry are required'); + mkdirSync(evidence, { recursive: true }); + const platform = hostPlatform(); + const scratch = realpathSync(mkdtempSync(join(realpathSync(evidence), `${suite}-`))); + const home = join(scratch, 'home'); + mkdirSync(home); + const ledger = new Ledger(platform); + const context: Context = { + ledger, platform, scratch, clis: clis(realpathSync(native), realpathSync(npmEntry), home), + python: values.python, portable: values.portable, nativeGuest: values['native-guest'], notes: {}, + }; + let error: string | undefined; + try { suites[suite](context); } catch (failure) { error = failure instanceof Error ? failure.message : String(failure); log(error); } + const failed = error !== undefined || ledger.rows.some(row => row.result === 'failed'); + const receipt = { + profile: 'morphir/extension-qualification', contractVersion: '0.1.0', suite, + state: failed ? 'failed' : 'passed', platform, + cli: Object.fromEntries(context.clis.map(cli => [cli.name, cli.build])), + ...(error ? { error } : {}), notes: context.notes, rows: ledger.rows, + }; + const path = join(evidence, `${suite}.json`); + writeFileSync(path, `${JSON.stringify(receipt, null, 2)}\n`); + log(`${suite}: ${receipt.state}; receipt ${path}`); + return failed ? 1 : 0; +} + +if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) { + process.exitCode = main(process.argv.slice(2)); +} diff --git a/apps/morphir/scripts/extism-python-api.py b/apps/morphir/scripts/extism-python-api.py deleted file mode 100644 index 5b7d9c201..000000000 --- a/apps/morphir/scripts/extism-python-api.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Independent Python API and generated-consumer checks for the CLI fixture.""" -from __future__ import annotations - -import json -import sys -import hashlib -import importlib -from importlib.metadata import version as package_version -from pathlib import Path - -from morphir.ir.v3 import codec as v3_codec -from morphir.ir.v4 import codec as v4_codec -from morphir_tools.python import CompileFailure, IrVersion, SourceDocument, compile_sources, native_compiler - -source, compiled, generated, version = (Path(sys.argv[1]), Path(sys.argv[2]), Path(sys.argv[3]), sys.argv[4]) -info = native_compiler() -assert info.compiler_version == sys.argv[5], info -result = compile_sources( - [SourceDocument(p.relative_to(source).as_posix(), p.read_text()) for p in sorted(source.rglob("*.py"))], - package="acme/example", ir_version=IrVersion.V3 if version == "3" else IrVersion.V4, -) -if isinstance(result, CompileFailure): - raise AssertionError(result.diagnostics) -codec = v3_codec if version == "3" else v4_codec -assert codec.decode_versioned_distribution(compiled.read_text()) == result.distribution -for file in generated.rglob("*.py"): - compile(file.read_text(), str(file), "exec") -sys.path.insert(0, str(generated)) -# Generated module paths follow the source package/module names. Find the module -# defining the concrete fixture functions without executing arbitrary test input. -rule_files = [p for p in generated.rglob("*.py") if "def choose(" in p.read_text()] -assert len(rule_files) == 1, rule_files -name = ".".join(rule_files[0].relative_to(generated).with_suffix("").parts) -rules = importlib.import_module(name) -assert rules.choose(True, 5, 7) == 5 -assert rules.choose(False, 5, 7) == 7 -assert rules.twice_identity(9) == 9 -assert rules.large_value() == 9007199254740993 -assert rules.pair(1.0, 2.0) == (1.0, 2.0) -native_module = importlib.import_module("morphir_python_native._native") -native_path = Path(native_module.__file__) -print(json.dumps({"python": sys.version, "nativeCompiler": str(info), "nativeModuleSha256": hashlib.sha256(native_path.read_bytes()).hexdigest(), "packages": {name: package_version(name) for name in ("morphir", "morphir-python", "morphir-python-native")}, "apiParity": True, "consumer": True})) diff --git a/apps/morphir/scripts/test-extism-python.ts b/apps/morphir/scripts/test-extism-python.ts deleted file mode 100644 index 76123dd4a..000000000 --- a/apps/morphir/scripts/test-extism-python.ts +++ /dev/null @@ -1,86 +0,0 @@ -import assert from 'node:assert/strict'; -import {createHash} from 'node:crypto'; -import {spawnSync} from 'node:child_process'; -import {cpSync,existsSync,mkdirSync,mkdtempSync,readFileSync,readdirSync,realpathSync,rmSync,statSync,writeFileSync} from 'node:fs'; -import {tmpdir} from 'node:os'; -import {dirname,join,resolve} from 'node:path'; -import {fileURLToPath} from 'node:url'; - -const repository=fileURLToPath(new URL('../../../',import.meta.url)); -const bundle=realpathSync(process.env.MORPHIR_EXTISM_BUNDLE??join(repository,'.dev/extism/python-bundle')); -const hostPinPath=process.env.MORPHIR_EXTISM_HOST_PIN??join(repository,'.dev/native-services/host-pin.json'); -const hostPin=JSON.parse(readFileSync(hostPinPath,'utf8')); -const hostBuild=JSON.parse(readFileSync(join(dirname(hostPinPath),'evidence.json'),'utf8')); -assert.deepEqual(hostBuild.host,hostPin,'host build receipt must bind the invoked executable'); -const python=process.env.MORPHIR_EXTISM_PYTHON; -assert.ok(python,'MORPHIR_EXTISM_PYTHON must name an independently installed matching morphir-tools Python API'); -const runtime=realpathSync(process.execPath),sha=(p:string)=>createHash('sha256').update(readFileSync(p)).digest('hex'); -const root=realpathSync(mkdtempSync(join(tmpdir(),'morphir-extism-python-'))); -const source=join(repository,'apps/morphir/fixtures/extism-python/v1'); -const descriptor=JSON.parse(readFileSync(join(bundle,'release.json'),'utf8')); -const wasm=descriptor.artifacts.find((a:{runtime:string})=>a.runtime==='wasm'); -assert.equal(sha(join(bundle,wasm.filename)),wasm.sha256); -assert.equal(wasm.claims?.capabilities?.frontend?.multiDocument,true,'full qualification requires an explicitly multi-document Python bundle'); -const native=resolve(process.env.MORPHIR_EXTISM_NATIVE??join(repository,'_build/native/debug/build/morphir/morphir/morphir.exe')); -const release=resolve(process.env.MORPHIR_EXTISM_RELEASE??join(repository,'_build/native/release/build/morphir/morphir/morphir.exe')); -const helpers=join(repository,'apps/morphir/build-provider'); -const required=['extism.mjs','supervision.mjs','windows-job.mjs']; -const results:unknown[]=[]; -const durations:number[]=[]; -function run(program:string,args:string[],status=0,cwd=root){ - const started=performance.now(); - const child=spawnSync(program,args,{cwd,env:{...process.env,NODE_DISABLE_COMPILE_CACHE:'1',MORPHIR_HOME:join(root,'home'),TMPDIR:join(root,'temporary'),HTTP_PROXY:'http://127.0.0.1:9',HTTPS_PROXY:'http://127.0.0.1:9',ALL_PROXY:'http://127.0.0.1:9'},encoding:'utf8',timeout:120000,maxBuffer:20*1024*1024}); - durations.push(performance.now()-started);assert.equal(child.status,status,JSON.stringify(args)+'\n'+child.stdout+'\n'+child.stderr);return child; -} -try{ - mkdirSync(join(root,'temporary'));mkdirSync(join(root,'home'));mkdirSync(join(root,'retained')); - cpSync(bundle,join(root,'retained/python'),{recursive:true}); - // No invocation uses the acquisition directory after the offline snapshot. - const retained=join(root,'retained/python'); - cpSync(source,join(root,'src'),{recursive:true}); - const packed=run('npm',['pack','--ignore-scripts','--pack-destination',root],0,join(repository,'apps/morphir')); - const tarball=join(root,packed.stdout.trim().split('\n').at(-1)!); - mkdirSync(join(root,'npm'));run('tar',['-xzf',tarball,'-C',join(root,'npm')]); - mkdirSync(join(root,'native-package'));cpSync(release,join(root,'native-package/morphir')); - const launchers=[ - {name:'source-native',program:native,prefix:[] as string[],helper:helpers}, - {name:'source-js',program:runtime,prefix:[join(repository,'apps/morphir/bin/morphir.js')],helper:helpers}, - {name:'packaged-native',program:join(root,'native-package/morphir'),prefix:[] as string[],helper:join(root,'npm/package/build-provider')}, - {name:'packaged-npm',program:runtime,prefix:[join(root,'npm/package/bin/morphir.js')],helper:join(root,'npm/package/build-provider')}, - ]; - for(const launcher of launchers){ - const pin={contractVersion:'0.1.0',binding:'extism-wasm',executionProfile:'morphir/extism-mep-0.1/v1',host:hostPin,runtime:{executable:runtime,version:process.version,sha256:sha(runtime)},helper:{root:launcher.helper,modules:Object.fromEntries(required.map(n=>[n,sha(join(launcher.helper,n))]))},artifact:{directory:retained,descriptorSha256:sha(join(retained,'release.json')),wasmSha256:wasm.sha256,extensionId:descriptor.extensionId,version:descriptor.version},origin:'finos/morphir-python release '+descriptor.version}; - const pinFile=join(root,launcher.name+'.json');writeFileSync(pinFile,JSON.stringify(pin)); - const controls=['--extension-pin',pinFile,'--extension-helper',join(launcher.helper,'extism.mjs'),'--json']; - const invoke=(args:string[],status=0)=>{const child=run(launcher.program,[...launcher.prefix,'extension',...args,...controls],status);const report=JSON.parse(child.stdout);assert.equal(report.profile,'morphir/command-result');assert.equal(report.exitCode,status);return {report,stderr:child.stderr};}; - const described=invoke(['describe']).report;assert.equal(described.result.session.extension.id,'morphir-python');assert.equal(described.result.session.provenance,'asserted'); - const help=JSON.parse(run(launcher.program,[...launcher.prefix,'extension','describe','--help','--json']).stdout);assert.ok(help.usage); - run(launcher.program,[...launcher.prefix,'extension','describe','--unknown-extism-option','--json'],2); - const cancel=join(root,launcher.name+'-cancel');writeFileSync(cancel,'');const cancelled=invoke(['describe','--cancel-file',cancel],130).report;assert.equal(cancelled.error.code,'execution.cancelled');assert.ok(!('result' in cancelled)); - const versions=[]; - for(const version of ['3','4']){ - const out=join(root,launcher.name+'-v'+version),generated=out+'-python',again=out+'-again'; - const compiled=invoke(['compile',join(root,'src'),'--package','acme/example','--ir-version',version,'--output',out]).report; - assert.equal(compiled.result.session.negotiated.protocolVersion,'0.1'); - const model=JSON.parse(readFileSync(join(out,'morphir-ir.json'),'utf8'));assert.equal(model.formatVersion,Number(version)); - invoke(['generate',join(out,'morphir-ir.json'),'--ir-version',version,'--output',generated]); - const parity=JSON.parse(run(python!,[join(repository,'apps/morphir/scripts/extism-python-api.py'),join(root,'src'),join(out,'morphir-ir.json'),generated,version,descriptor.version]).stdout); - invoke(['compile',generated,'--package','acme/example','--ir-version',version,'--output',again]); - assert.deepEqual(JSON.parse(readFileSync(join(again,'morphir-ir.json'),'utf8')),model); - const invalid=invoke(['generate',join(out,'morphir-ir.json'),'--ir-version',version==='3'?'4':'3','--output',out+'-mismatch'],1).report; - assert.equal(invalid.state,'failed');assert.ok(!('result' in invalid));assert.equal(existsSync(out+'-mismatch'),false); - versions.push({version,parity,modelSha256:sha(join(out,'morphir-ir.json')),generated:readdirSync(generated)}); - } - for(const args of [ ['compile',join(root,'src'),'--package','acme/example','--output',join(root,'missing')], ['compile',join(root,'src'),'--package','acme/example','--ir-version','3.1.0','--output',join(root,'unsupported')], ['compile',join(root,'src'),'--package','acme/example','--ir-version','3','--model-format','ion','--output',join(root,'ion')] ]) assert.equal(invoke(args,1).report.state,'failed'); - const bad={...pin,artifact:{...pin.artifact,wasmSha256:'0'.repeat(64)}};writeFileSync(pinFile,JSON.stringify(bad));assert.equal(invoke(['describe'],1).report.state,'failed');writeFileSync(pinFile,JSON.stringify(pin)); - writeFileSync(join(root,'broken.py'),'def broken(:\n');const diagnosed=invoke(['compile',join(root,'broken.py'),'--package','acme/example','--ir-version','3','--output',join(root,launcher.name+'-broken')],1);assert.ok(diagnosed.report.diagnostics.some((d:{code?:string})=>d.code?.startsWith('PY'))); - assert.deepEqual(readdirSync(join(root,'temporary')),[],'owned scratch was reaped and cleaned'); - results.push({name:launcher.name,programSha256:sha(launcher.program),entrypoint:launcher.prefix.map(p=>({file:p,sha256:sha(p)})),helpers:pin.helper.modules,versions}); - } - const sourceFiles=['apps/morphir/cli/extism.mbt','apps/morphir/cli/extension.mbt','apps/morphir/cli/arguments.mbt','apps/morphir/native-services/main.rs','apps/morphir/native-services/extism.rs','apps/morphir/native-services/extism_tests.rs','apps/morphir/native-services/Cargo.lock','apps/morphir/dist/morphir.js','apps/morphir/build-provider/extism.mts','apps/morphir/build-provider/test-extism.mts','apps/morphir/fixtures/extism-python/v1/acme/models.py','apps/morphir/fixtures/extism-python/v1/acme/rules.py','apps/morphir/scripts/extism-python-api.py','scripts/extism-python.mbtx']; - const receipt={profile:'morphir/extism-python-qualification',contractVersion:'0.1.0',state:'succeeded',platform:process.platform,architecture:process.arch,node:process.version,runtimeSha256:sha(runtime),python:{executable:python,sha256:sha(python!)},host:hostPin,hostBuild:{sourceRevision:hostBuild.sourceRevision,sharedRustTree:hostBuild.sharedRustTree,target:hostBuild.target,rust:hostBuild.rust,moonbit:hostBuild.moonbit,cargoLockSha256:hostBuild.cargoLockSha256},hostBytes:statSync(hostPin.executable).size,bundle:{descriptorSha256:sha(join(bundle,'release.json')),wasmSha256:wasm.sha256,extensionId:descriptor.extensionId,version:descriptor.version,gitCommit:descriptor.gitCommit},checkDefinitionSha256:sha(fileURLToPath(import.meta.url)),sourceFiles:Object.fromEntries(sourceFiles.map(p=>[p,sha(join(repository,p))])),sourceRevision:spawnSync('git',['rev-parse','HEAD'],{cwd:repository,encoding:'utf8'}).stdout.trim(),elapsedCallsMs:durations,results}; - const reusablePin={contractVersion:'0.1.0',binding:'extism-wasm',executionProfile:'morphir/extism-mep-0.1/v1',host:hostPin,runtime:{executable:runtime,version:process.version,sha256:sha(runtime)},helper:{root:helpers,modules:Object.fromEntries(required.map(n=>[n,sha(join(helpers,n))]))},artifact:{directory:bundle,descriptorSha256:sha(join(bundle,'release.json')),wasmSha256:wasm.sha256,extensionId:descriptor.extensionId,version:descriptor.version},origin:'finos/morphir-python release '+descriptor.version}; - mkdirSync(join(repository,'.dev/extism'),{recursive:true}); - writeFileSync(join(repository,'.dev/extism/python-pin.json'),JSON.stringify(reusablePin,null,2)+'\n'); - const destination=join(repository,'.dev/extism/qualification.json');writeFileSync(destination,JSON.stringify(receipt,null,2)+'\n');console.log(JSON.stringify(receipt)); -}finally{rmSync(root,{recursive:true,force:true});} diff --git a/docs/MISE_TASKS.md b/docs/MISE_TASKS.md index 2c984fe7a..f90da2c0d 100644 --- a/docs/MISE_TASKS.md +++ b/docs/MISE_TASKS.md @@ -77,7 +77,12 @@ compiler/core pair. | `mise run test:workbench-capability-execution-mck` | Opt-in exact draft.19 immutable Scheme execution traces in Node/Chromium/native debug/release; requires explicit parent runner/kit and Workbench Chromium dependencies. Actual isolated-worker acceptance is separate | All | | `mise run test:conformance` | Compare versioned independent Ion expectations, live Scheme and explicitly pinned optional Rust; retain separate coverage receipts | All | | `mise run test:cli` | Run CLI target and npm package smoke tests | All | -| `mise run test:cli-extism-python -- --shared /absolute/morphir --bundle /absolute/python-bundle --python /absolute/venv/bin/python --json` | Opt-in pinned Python Extism IR 3/4 qualification through source and packaged CLIs; requires an existing matching Python API and bundle. It still writes a retired pin and is rewritten for the worker host in group 7 of `host-extensions-as-workers`; see [qualification](../apps/morphir/docs/extension.md#qualification) | Waits for the WASM worker host | +| `mise run test:cli-extension-elm -- --json` | Qualify the pinned Elm extension `v0.4.1` executables on the worker host from the native and npm CLIs: describe, compile of the Elm fixture, and no Node on the native run's PATH. See [qualification](../apps/morphir/docs/extension.md#qualification) | macOS, Linux; manual | +| `mise run test:cli-extism-python -- --json` | Qualify the pinned Python extension `0.4.1` (one document, multi-document refusal) and `0.4.2` (IR 3 and 4 compile, generate, consumer, recompile) from the native and npm CLIs; `--python PATH` names Python 3.12 or later for the consumer | macOS, Linux; manual | +| `mise run test:cli-native-prerequisites -- --json` | Qualify the native MoonBit guest as a process worker from both CLIs with the shared kit's source, consumer and JSON artifacts, and run the shared MEP kit against it | macOS, Linux; manual | +| `mise run test:portable-mep-cli -- --json` | Qualify the portable MoonBit guest (`morphir-bytes-v1`) from both CLIs, and run the shared MEP kit against it through the native CLI's worker mode | macOS, Linux; manual | +| `mise run test:extension-conformance -- --json` | Run the `morphir-extension-sdk` fixture guest in `apps/morphir/fixtures/sdk-guest` from both CLIs: imports, claims, compile, generate and host functions | macOS, Linux; manual | +| `mise run test:extension-qualification -- --json` | Run all five suites above and write one receipt for each | macOS, Linux; manual | | `mise run test:moonbit-frontend` | Compare original Boolean MoonBit, Scheme and generated libraries across four targets in debug and release; requires pinned `MOON_HOME` and retains identities | All | | `mise run test:supervision` | Verify process-tree cleanup, cancellation, deadlines and bounded diagnostics on Linux, macOS and Windows | All | | `mise run test:embedding` | Install embedding dependencies and Chromium, then test Node, installed npm and browser hosts | All | diff --git a/scripts/extension-qualification.mbtx b/scripts/extension-qualification.mbtx new file mode 100644 index 000000000..55263a0f9 --- /dev/null +++ b/scripts/extension-qualification.mbtx @@ -0,0 +1,451 @@ +---- +import { + "moonbitlang/core/argparse", + "moonbitlang/core/env", + "moonbitlang/core/json", + "moonbitlang/async@0.22.7", + "moonbitlang/async@0.22.7/fs", + "moonbitlang/async@0.22.7/process", + "moonbitlang/async@0.22.7/stdio", + "moonbitlang/x@0.5.5/crypto", + "moonbitlang/x@0.5.5/sys", +} +---- + +// Qualification of the published extensions and the conformance fixture guest +// on the CLI's worker host (group 7 of the OpenSpec change +// host-extensions-as-workers). It builds the native CLI and the npm package, +// and the MoonBit guests when a suite needs them, then runs +// apps/morphir/scripts/extension-qualification.ts once for each suite. That +// driver downloads the pinned artifacts into new directories, checks their +// SHA-256, runs every operation from the native and the npm CLI and writes one +// receipt for each suite. This script is manual: no PR or merge-queue job runs +// it. + +///| +/// One qualification suite of the driver. +enum Suite { + Elm + Python + MoonbitNative + MoonbitPortable + Conformance +} derive(Eq) + +///| +let all_suites : Array[Suite] = [ + Elm, + Python, + MoonbitNative, + MoonbitPortable, + Conformance, +] + +///| +/// The suites that a SUITE argument selects: one suite, or every suite for +/// `all`. +fn Suite::select(text : String) -> Array[Suite]? { + match text { + "all" => Some(all_suites) + "elm" => Some([Elm]) + "python" => Some([Python]) + "moonbit-native" => Some([MoonbitNative]) + "moonbit-portable" => Some([MoonbitPortable]) + "conformance" => Some([Conformance]) + _ => None + } +} + +///| +fn Suite::name(self : Suite) -> String { + match self { + Elm => "elm" + Python => "python" + MoonbitNative => "moonbit-native" + MoonbitPortable => "moonbit-portable" + Conformance => "conformance" + } +} + +///| +let about : String = + #|Qualify the published extensions and the SDK conformance fixture guest on the CLI's worker host, from the native CLI and the npm CLI (an unpacked npm pack of apps/morphir). SUITE is elm (Elm extension v0.4.1 executables), python (Python extension 0.4.1 and 0.4.2), moonbit-native and moonbit-portable (the MoonBit guest as a process worker or a morphir-bytes-v1 WASM worker, with the shared MEP kit), conformance (the morphir-extension-sdk fixture guest in apps/morphir/fixtures/sdk-guest) or all. + #| + #|The script builds the inputs (moon build of apps/morphir and apps/morphir-extension, bun run build, npm pack, and scripts/package-mep-extension.mbtx for moonbit-portable), then runs apps/morphir/scripts/extension-qualification.ts for each suite. The driver downloads each pinned artifact of apps/morphir/fixtures/qualification/published.json into a new, empty directory and uses it only when its SHA-256 equals the pin. It writes /.json, one receipt with one row for each operation: extension, version, cli (native, npm or mck), platform, operation, result (passed, failed or known-failure, with the finding and its follow-up bead), guest SHA-256 and output digests. A known failure is an operation that fails for a recorded cause outside the host; it does not fail the suite, and it fails the suite when it passes. + #| + #|Build, download and driver output goes to stderr. Without --json, stdout has one summary line for each suite. With --json, stdout is one morphir/command-result 0.1.0 value written after every receipt: result {evidence, suites: [{suite, state, receiptPath, receiptSha256, passed, knownFailures, failed}], receipts: []}. A failure has error {code, message} instead of result: qualification_failed (exit 1) when a suite failed, step_failed with the exit status of a failed build step, invalid_arguments (exit 2) for invalid arguments. --help prints this help and runs nothing; --help --json returns it as result.help. + +///| +fn command() -> @argparse.Command { + @argparse.Command( + "extension-qualification", + about~, + disable_help_flag=true, + disable_help_subcommand=true, + positionals=[ + @argparse.PositionArg( + "SUITE", + about="elm, python, moonbit-native, moonbit-portable, conformance or all", + ), + ], + flags=[ + @argparse.FlagArg("json", about="Write one command-result on stdout"), + @argparse.FlagArg("help", short='h', about="Print the help; run nothing"), + ], + options=[ + @argparse.OptionArg( + "evidence", + about="Directory for receipts and scratch files; default .dev/extension-qualification/evidence", + ), + @argparse.OptionArg( + "python", + about="Python 3.12 or later for the Python consumer check; default: uv python find '>=3.12'", + ), + ], + ) +} + +///| +suberror StepFailed { + StepFailed(Int32, String) +} + +///| +/// Runs one build step with its output on stderr. +async fn step( + program : String, + args : Array[String], + env? : Map[String, String], +) -> Unit { + let code = @process.run( + program, + args, + extra_env=env.unwrap_or(Map([])), + stdout=@stdio.stderr, + stderr=@stdio.stderr, + ) + guard code == 0 else { + raise StepFailed( + if code > 0 { + code + } else { + 1 + }, + "\{program} \{args.join(" ")} failed with status \{code}", + ) + } +} + +///| +async fn sha256_of(path : String) -> String { + @crypto.bytes_to_hex_string(@crypto.sha256(@fs.read_file(path).binary())) +} + +///| +fn field(value : Json, name : String) -> Json { + match value { + Object(fields) => fields.get(name).unwrap_or(Json::null()) + _ => Json::null() + } +} + +///| +/// The number of receipt rows with a result. +fn count(receipt : Json, result : String) -> Int32 { + match field(receipt, "rows") { + Array(rows) => + rows.filter(row => field(row, "result") == Json::string(result)).length() + _ => 0 + } +} + +///| +/// The summary of one receipt. +fn summary( + suite : Suite, + path : String, + digest : String, + receipt : Json, +) -> Json { + { + "suite": suite.name(), + "state": field(receipt, "state"), + "receiptPath": path, + "receiptSha256": digest, + "passed": count(receipt, "passed"), + "knownFailures": count(receipt, "known-failure"), + "failed": count(receipt, "failed"), + } +} + +///| +/// Python 3.12 or later for the consumer check, from uv when it is installed. +async fn find_python() -> String? { + let (code, out, _) = @process.collect_output("uv", [ + "python", "find", ">=3.12", + ]) catch { + _ => return None + } + let path = out.text().trim().to_owned() + if code == 0 && path != "" { + Some(path) + } else { + None + } +} + +///| +async fn execute( + suites : Array[Suite], + evidence : String, + python : String?, +) -> (Bool, Json) { + let root = @fs.realpath(".") + @fs.mkdir(evidence, recursive=true) + let evidence = @fs.realpath(evidence) + let built = "\{root}/_build/native/release/build" + step("moon", ["build", "--target", "native", "--release", "apps/morphir"]) + step("bun", ["install", "--frozen-lockfile"]) + step("bun", ["run", "--cwd", "apps/morphir", "build"]) + // The npm CLI is the package that npm pack makes, unpacked in a new + // directory, as a user installs it. + let npm = "\{evidence}/npm" + if @fs.exists(npm) { + @fs.rmdir(npm, recursive=true) + } + @fs.mkdir(npm, recursive=true) + step("npm", [ + "pack", "--ignore-scripts", "--silent", "--pack-destination", npm, "./apps/morphir", + ]) + step("tar", ["-xzf", "\{npm}/morphir-morphir-0.1.0.tgz", "-C", npm]) + let moonbit = suites.contains(MoonbitNative) || + suites.contains(MoonbitPortable) + if moonbit { + step("moon", [ + "build", "--target", "native", "--release", "apps/morphir-extension", + ]) + } + let portable = "\{evidence}/portable-mep" + if suites.contains(MoonbitPortable) { + step("bun", [ + "install", "--frozen-lockfile", "--cwd", "pkgs/morphir-host/embedding", + ]) + // The packaging refuses an existing destination. + if @fs.exists(portable) { + @fs.rmdir(portable, recursive=true) + } + step("moonx", ["scripts/package-mep-extension.mbtx"], env={ + "MORPHIR_MEP_ARTIFACTS": portable, + }) + } + let python = match python { + Some(path) => Some(path) + None => if suites.contains(Python) { find_python() } else { None } + } + let summaries : Array[Json] = [] + let receipts : Array[Json] = [] + let mut passed = true + for suite in suites { + let receipt_path = "\{evidence}/\{suite.name()}.json" + if @fs.exists(receipt_path) { + @fs.remove(receipt_path) + } + let args = [ + "apps/morphir/scripts/extension-qualification.ts", + "--suite", + suite.name(), + "--evidence", + evidence, + "--native", + "\{built}/morphir/morphir/morphir.exe", + "--npm-entry", + "\{npm}/package/bin/morphir.js", + "--native-guest", + "\{built}/finos/morphir-extension/morphir-extension.exe", + "--portable", + "\{portable}/extension", + ] + if python is Some(path) { + args.append(["--python", path]) + } + let code = @process.run( + "node", + args, + stdout=@stdio.stderr, + stderr=@stdio.stderr, + ) + guard @fs.exists(receipt_path) else { + raise StepFailed( + if code > 0 { + code + } else { + 1 + }, + "the driver wrote no receipt for \{suite.name()} (status \{code})", + ) + } + let receipt = @fs.read_file(receipt_path).json() + let item = summary(suite, receipt_path, sha256_of(receipt_path), receipt) + if code != 0 || field(receipt, "state") != Json::string("passed") { + passed = false + } + summaries.push(item) + receipts.push(receipt) + } + ( + passed, + { + "evidence": evidence, + "suites": Json::array(summaries), + "receipts": Json::array(receipts), + }, + ) +} + +///| +fn document(state : String, code : Int32, body : (String, Json)) -> String { + let fields : Map[String, Json] = { + "profile": "morphir/command-result", + "contractVersion": "0.1.0", + "command": "extension-qualification", + "state": state, + "exitCode": code, + "diagnostics": [], + } + fields[body.0] = body.1 + Json::object(fields).stringify() + "\n" +} + +///| +async fn refuse( + json : Bool, + code : Int32, + kind : String, + message : String, +) -> Unit { + if json { + @stdio.stdout.write( + document("failed", code, ("error", { "code": kind, "message": message })), + ) + } else { + @stdio.stderr.write("extension-qualification: \{message}\n") + } + @sys.exit(code) +} + +///| +fn summary_line(item : Json) -> String { + let text = fn(name : String) { + match field(item, name) { + String(value) => value + Number(value, ..) => value.to_int().to_string() + _ => "" + } + } + "\{text("suite")}: \{text("state")} (\{text("passed")} passed, \{text("knownFailures")} known failures, \{text("failed")} failed) \{text("receiptPath")}" +} + +///| +async fn main { + let json = @env.args().contains("--json") + let matches = command().parse(argv=@env.args()[1:]) catch { + error => { + refuse(json, 2, "invalid_arguments", error.to_string()) + return + } + } + let json = matches.flags.get("json").unwrap_or(false) + if matches.flags.get("help").unwrap_or(false) { + if json { + @stdio.stdout.write( + document( + "succeeded", + 0, + ("result", { "help": command().render_help() }), + ), + ) + } else { + @stdio.stdout.write(command().render_help() + "\n") + } + return + } + let suites = match matches.values.get("SUITE") { + Some([text, ..]) => Suite::select(text) + _ => None + } + guard suites is Some(suites) else { + refuse( + json, 2, "invalid_arguments", "SUITE must be elm, python, moonbit-native, moonbit-portable, conformance or all", + ) + return + } + let evidence = match matches.values.get("evidence") { + Some([path, ..]) => path + _ => ".dev/extension-qualification/evidence" + } + let python = match matches.values.get("python") { + Some([path, ..]) => Some(path) + _ => None + } + let (passed, result) = execute(suites, evidence, python) catch { + StepFailed(code, message) => { + refuse(json, code, "step_failed", message) + return + } + error => { + refuse(json, 1, "command_failed", error.to_string()) + return + } + } + guard field(result, "suites") is Array(items) else { return } + if !passed { + let failed = items + .filter(item => field(item, "state") != Json::string("passed")) + .map(summary_line) + refuse( + json, + 1, + "qualification_failed", + "a suite failed: " + failed.join("; "), + ) + return + } + if json { + @stdio.stdout.write(document("succeeded", 0, ("result", result))) + } else { + for item in items { + @stdio.stdout.write(summary_line(item) + "\n") + } + } +} + +///| +test "a SUITE argument selects suites" { + assert_eq(Suite::select("all").map(suites => suites.length()), Some(5)) + assert_true(Suite::select("python") is Some([Python])) + assert_true(Suite::select("moonbit-portable") is Some([MoonbitPortable])) + assert_true(Suite::select("rust") is None) + assert_eq(all_suites.map(Suite::name), [ + "elm", "python", "moonbit-native", "moonbit-portable", "conformance", + ]) +} + +///| +test "a summary counts the rows of each result" { + let receipt : Json = { + "state": "passed", + "rows": [ + { "result": "passed" }, + { "result": "passed" }, + { "result": "known-failure" }, + ], + } + let item = summary(Elm, "/e/elm.json", "ab", receipt) + assert_eq(field(item, "passed"), Json::number(2.0)) + assert_eq(field(item, "knownFailures"), Json::number(1.0)) + assert_eq(field(item, "failed"), Json::number(0.0)) + assert_eq( + summary_line(item), + "elm: passed (2 passed, 1 known failures, 0 failed) /e/elm.json", + ) +} diff --git a/scripts/extism-python.mbtx b/scripts/extism-python.mbtx deleted file mode 100644 index 3b18a7225..000000000 --- a/scripts/extism-python.mbtx +++ /dev/null @@ -1,711 +0,0 @@ ----- -import { - "moonbitlang/core/argparse", - "moonbitlang/core/env", - "moonbitlang/core/json", - "moonbitlang/async@0.22.7", - "moonbitlang/async@0.22.7/fs", - "moonbitlang/async@0.22.7/process", - "moonbitlang/async@0.22.7/stdio", - "moonbitlang/x@0.5.5/crypto", - "moonbitlang/x@0.5.5/sys", -} ----- - -///| -/// Opt-in qualification of an existing Python Extism bundle. Acquisition and -/// dependency installation are explicit prerequisites, never side effects. -suberror CommandFailure { - CommandFailure(Int32, String, String) -} - -///| -async fn checked( - program : String, - args : Array[String], - environment~ : Map[String, String] = Map([]), -) -> Unit { - let moon_home = @env.get_env_var("MOON_HOME").unwrap_or("") - let executable = if program == "moon" && moon_home != "" { - moon_home + "/bin/moon" - } else { - program - } - let variables = environment.copy() - if moon_home != "" { - variables["PATH"] = moon_home + - "/bin:" + - @env.get_env_var("PATH").unwrap_or("") - } - let status = @process.run( - executable, - args, - extra_env=variables, - stdout=@stdio.stderr, - stderr=@stdio.stderr, - ) - guard status == 0 else { - raise CommandFailure( - if status > 0 { - status - } else { - 1 - }, - "extism.qualification_failed", - program + " failed with status " + status.to_string(), - ) - } -} - -///| -fn required(matches : @argparse.Matches, key : String) -> String raise { - let value = match matches.values.get(key) { - Some([value]) => value - _ => "" - } - guard value.has_prefix("/") && !value.contains("\u0000") else { - raise CommandFailure( - 1, - "extism.invalid_input", - "--" + key + " must name an explicit absolute local path", - ) - } - value -} - -///| -fn field(value : Json, key : String) -> Json { - match value { - Object(fields) => fields.get(key).unwrap_or(Json::null()) - _ => Json::null() - } -} - -///| -async fn digest(path : String) -> String { - @crypto.bytes_to_hex_string(@crypto.sha256(@fs.read_file(path).binary())) -} - -///| -async fn capture(program : String, args : Array[String]) -> String { - let (status, output, errors) = @process.collect_output(program, args) - if errors.text() != "" { - @stdio.stderr.write(errors.text()) - } - guard status == 0 else { - raise CommandFailure( - if status > 0 { - status - } else { - 1 - }, - "extism.qualification_failed", - "Unable to inspect " + program + " for candidate-bound qualification", - ) - } - output.text() -} - -///| -/// The caller takes this snapshot before the real-bundle suite and compares it -/// again afterwards. A source/artifact change while tests run cannot be blessed -/// by a receipt that merely hashes the new bytes after the fact. -async fn candidate(root : String, bundle : String, python : String) -> Json { - let node = @json.parse( - capture("node", [ - "-p", "JSON.stringify({platform:process.platform,architecture:process.arch,node:process.version,executable:process.execPath})", - ]), - ) - guard field(node, "executable") is String(runtime) else { - raise CommandFailure( - 1, "extism.invalid_receipt", "Node runtime identity is missing", - ) - } - let host = @json.parse( - @fs.read_file(root + "/.dev/native-services/host-pin.json").text(), - ) - guard field(host, "executable") is String(host_program) && - field(host, "sha256") == Json::string(digest(host_program)) else { - raise CommandFailure( - 1, "extism.invalid_receipt", "Pinned host bytes changed", - ) - } - let host_evidence = @json.parse( - @fs.read_file(root + "/.dev/native-services/evidence.json").text(), - ) - guard field(host_evidence, "host") == host && - field(host_evidence, "sourceRevision") == field(host, "sourceRevision") && - field(host_evidence, "cargoLockSha256") == - Json::string(digest(root + "/.dev/native-services/Cargo.lock")) else { - raise CommandFailure( - 1, "extism.invalid_receipt", "Host build evidence does not match its executable or lock", - ) - } - let host_build : Map[String, Json] = Map([]) - for - key in [ - "sourceRevision", "sharedRustTree", "target", "rust", "moonbit", "cargoLockSha256", - ] { - guard field(host_evidence, key) is String(text) && text != "" else { - raise CommandFailure( - 1, - "extism.invalid_receipt", - "Missing host build identity: " + key, - ) - } - host_build[key] = Json::string(text) - } - let descriptor = @json.parse(@fs.read_file(bundle + "/release.json").text()) - guard field(descriptor, "artifacts") is Array(artifacts) && - artifacts - .iter() - .find_first(a => field(a, "runtime") == Json::string("wasm")) - is Some(wasm) && - field(descriptor, "version") is String(version) && - field(wasm, "filename") is String(filename) && - filename != "" && - !filename.has_prefix("/") && - !filename.contains("\\") && - filename.split("/").all(part => part != "" && part != "." && part != "..") && - field(wasm, "sha256") == Json::string(digest(bundle + "/" + filename)) else { - raise CommandFailure( - 1, "extism.invalid_receipt", "Python WASM bytes changed", - ) - } - let descriptor_sha = digest(bundle + "/release.json") - let bundle_fields : Map[String, Json] = Map::from_array([ - ("descriptorSha256", Json::string(descriptor_sha)), - ("wasmSha256", field(wasm, "sha256")), - ("extensionId", field(descriptor, "extensionId")), - ("version", field(descriptor, "version")), - ]) - if descriptor is Object(fields) && fields.get("gitCommit") is Some(commit) { - bundle_fields["gitCommit"] = commit - } - let source_files : Map[String, Json] = Map([]) - for - path in [ - "apps/morphir/cli/extism.mbt", "apps/morphir/cli/extension.mbt", "apps/morphir/cli/arguments.mbt", - "apps/morphir/native-services/main.rs", "apps/morphir/native-services/extism.rs", - "apps/morphir/native-services/extism_tests.rs", "apps/morphir/native-services/Cargo.lock", - "apps/morphir/dist/morphir.js", "apps/morphir/build-provider/extism.mts", "apps/morphir/build-provider/test-extism.mts", - "apps/morphir/fixtures/extism-python/v1/acme/models.py", "apps/morphir/fixtures/extism-python/v1/acme/rules.py", - "apps/morphir/scripts/extism-python-api.py", "scripts/extism-python.mbtx", - ] { - source_files[path] = Json::string(digest(root + "/" + path)) - } - let modules : Map[String, Json] = Map([]) - for name in ["extism.mjs", "supervision.mjs", "windows-job.mjs"] { - modules[name] = Json::string( - digest(root + "/apps/morphir/build-provider/" + name), - ) - } - let runtime_sha = digest(runtime) - let entry_sha = digest(root + "/apps/morphir/bin/morphir.js") - let launchers : Json = { - "source-native": { - "programSha256": digest( - root + "/_build/native/debug/build/morphir/morphir/morphir.exe", - ), - "entrypointSha256": Json::null(), - }, - "source-js": { "programSha256": runtime_sha, "entrypointSha256": entry_sha }, - "packaged-native": { - "programSha256": digest( - root + "/_build/native/release/build/morphir/morphir/morphir.exe", - ), - "entrypointSha256": Json::null(), - }, - "packaged-npm": { - "programSha256": runtime_sha, - "entrypointSha256": entry_sha, - }, - } - let expected_pin : Json = { - "contractVersion": "0.1.0", - "binding": "extism-wasm", - "executionProfile": "morphir/extism-mep-0.1/v1", - "host": host, - "runtime": { - "executable": runtime, - "version": field(node, "node"), - "sha256": runtime_sha, - }, - "helper": { - "root": root + "/apps/morphir/build-provider", - "modules": Json::object(modules), - }, - "artifact": { - "directory": bundle, - "descriptorSha256": descriptor_sha, - "wasmSha256": field(wasm, "sha256"), - "extensionId": field(descriptor, "extensionId"), - "version": field(descriptor, "version"), - }, - "origin": "finos/morphir-python release " + version, - } - { - "platform": field(node, "platform"), - "architecture": field(node, "architecture"), - "node": field(node, "node"), - "runtimeSha256": runtime_sha, - "python": { "executable": python, "sha256": digest(python) }, - "host": host, - "hostBuild": Json::object(host_build), - "bundle": Json::object(bundle_fields), - "sourceRevision": capture("git", ["rev-parse", "HEAD"]).trim().to_owned(), - "sourceFiles": Json::object(source_files), - "checkDefinitionSha256": digest( - root + "/apps/morphir/scripts/test-extism-python.ts", - ), - "helpers": Json::object(modules), - "launchers": launchers, - "pin": expected_pin, - } -} - -///| -fn receipt_valid(receipt : Json, expected : Json) -> Bool { - if field(receipt, "profile") != - Json::string("morphir/extism-python-qualification") || - field(receipt, "contractVersion") != Json::string("0.1.0") || - field(receipt, "state") != Json::string("succeeded") { - return false - } - for - key in [ - "platform", "architecture", "node", "runtimeSha256", "python", "host", "hostBuild", - "bundle", "sourceRevision", "sourceFiles", "checkDefinitionSha256", - ] { - if field(receipt, key) != field(expected, key) { - return false - } - } - guard field(receipt, "results") is Array(results) && results.length() == 4 else { - return false - } - let seen : Map[String, Bool] = Map([]) - for result in results { - guard field(result, "name") is String(name) && - ["source-native", "source-js", "packaged-native", "packaged-npm"].contains( - name, - ) && - !seen.contains(name) else { - return false - } - seen[name] = true - let launcher = field(field(expected, "launchers"), name) - if field(result, "programSha256") != field(launcher, "programSha256") || - field(result, "helpers") != field(expected, "helpers") { - return false - } - guard field(result, "entrypoint") is Array(entries) else { return false } - match field(launcher, "entrypointSha256") { - Null => if !entries.is_empty() { return false } - String(hash) => - if entries.length() != 1 || - field(entries[0], "sha256") != Json::string(hash) { - return false - } - _ => return false - } - guard field(result, "versions") is Array(versions) && versions.length() == 2 else { - return false - } - let completed : Map[String, Bool] = Map([]) - for version in versions { - guard field(version, "version") is String(major) && - ["3", "4"].contains(major) && - !completed.contains(major) && - field(field(version, "parity"), "apiParity") == Json::boolean(true) && - field(field(version, "parity"), "consumer") == Json::boolean(true) && - field(version, "modelSha256") is String(hash) && - hash.length() == 64 && - hash.iter().all(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) && - field(version, "generated") is Array(files) && - !files.is_empty() else { - return false - } - completed[major] = true - } - } - true -} - -///| -async fn main { - let mut json_mode = @env.args().contains("--json") - try { - let schema = @argparse.Command( - "test:cli-extism-python", - about="Qualify a local Python Extism bundle with the pinned shared host, native and JS source/packaged CLIs. Requires existing dependencies, local bundle and independent matching Python API; never downloads or installs them. --json emits one morphir/command-result 0.1.0 document on stdout; progress and child output use stderr. Success returns receiptPath, receiptSha256 and pinPath for fresh checked evidence under .dev/extism/. Failure returns error {code,message} instead of result. Validation exits 1, syntax 2, positive child statuses are preserved. Help uses result.help and performs no builds.", - disable_help_flag=true, - disable_help_subcommand=true, - options=[ - @argparse.OptionArg( - "shared", - about="Absolute finos/morphir checkout at the pinned revision", - ), - @argparse.OptionArg( - "bundle", - about="Absolute corrected Python bundle with release.json and frontend.multiDocument=true", - ), - @argparse.OptionArg( - "python", - about="Absolute Python interpreter with the matching independent morphir-tools API installed", - ), - ], - flags=[@argparse.FlagArg("json"), @argparse.FlagArg("help", short='h')], - ) - let matches = schema.parse(argv=@env.args()[1:]) catch { - error => - raise CommandFailure(2, "extism.invalid_arguments", error.to_string()) - } - json_mode = matches.flags.get("json").unwrap_or(false) - if matches.flags.get("help").unwrap_or(false) { - if json_mode { - println( - ( - { - "profile": "morphir/command-result", - "contractVersion": "0.1.0", - "command": "test:cli-extism-python", - "state": "succeeded", - "exitCode": 0, - "diagnostics": [], - "result": { "help": schema.render_help() }, - } : Json).stringify(), - ) - } else { - println(schema.render_help()) - } - return - } - let shared = @fs.realpath(required(matches, "shared")) - let bundle = @fs.realpath(required(matches, "bundle")) - // Keep a virtualenv's interpreter path: resolving its symlink would lose - // pyvenv.cfg discovery and could accidentally select a different API. - let python = required(matches, "python") - guard @fs.exists(shared + "/.git") && - @fs.exists(bundle + "/release.json") && - @fs.exists(python) else { - raise CommandFailure( - 1, "extism.invalid_input", "Expected a local shared checkout and Python bundle release.json", - ) - } - let declared = @json.parse(@fs.read_file(bundle + "/release.json").text()) - guard field(declared, "artifacts") is Array(artifacts) && - artifacts - .iter() - .find_first(a => field(a, "runtime") == Json::string("wasm")) - is Some(wasm) && - field(field(field(wasm, "claims"), "capabilities"), "frontend") - is Object(frontend) && - frontend.get("multiDocument") == Some(Json::boolean(true)) else { - raise CommandFailure( - 1, "extism.invalid_input", "Full qualification requires a corrected Python bundle declaring frontend.multiDocument=true; older bundles are limited to one source document", - ) - } - qualification_receipt_states() - qualification_receipt_consumers() - checked( - "moon", - [ - "run", "scripts/native-services.mbtx", "--", "--shared", shared, "--json", - ], - environment={ "CARGO_NET_OFFLINE": "true", "RUSTUP_AUTO_INSTALL": "0" }, - ) - let root = @fs.realpath(".") - let host_evidence = @json.parse( - @fs.read_file(root + "/.dev/native-services/evidence.json").text(), - ) - guard host_evidence is Object(evidence) && - evidence.get("target") is Some(String(target)) && - target != "" else { - raise CommandFailure( - 1, "extism.invalid_receipt", "The pinned host build must identify its Rust target before adapter tests", - ) - } - checked( - "cargo", - [ - "+1.98.0", - "test", - "--locked", - "--manifest-path", - root + "/.dev/native-services/Cargo.toml", - "--bin", - "morphir-moonbit-native-services", - "--test", - "shared-restricted-extism", - "--", - "--include-ignored", - "--test-threads=1", - ], - environment={ - "CARGO_NET_OFFLINE": "true", - "RUSTUP_AUTO_INSTALL": "0", - "MORPHIR_NATIVE_TARGET": target, - "MORPHIR_TEST_GUEST": root + - "/_build/native/release/build/finos/morphir-extension/morphir-extension.exe", - "MORPHIR_EXTISM_TEST_BUNDLE": bundle, - }, - ) - checked("moon", ["build", "apps/morphir", "--target", "native"]) - checked("moon", ["build", "apps/morphir", "--target", "native", "--release"]) - checked("bun", ["run", "--cwd", "apps/morphir", "build"]) - checked("node", ["apps/morphir/build-provider/test-extism.mts"]) - for target in ["native", "js"] { - checked("moon", [ - "test", "apps/morphir/cli/extism_wbtest.mbt", "--target", target, - ]) - } - let expected = candidate(root, bundle, python) - let receipt_path = root + "/.dev/extism/qualification.json" - if @fs.exists(receipt_path) { - @fs.remove(receipt_path) - } - checked( - "node", - ["apps/morphir/scripts/test-extism-python.ts"], - environment=Map::from_array([ - ("MORPHIR_EXTISM_BUNDLE", bundle), - ("MORPHIR_EXTISM_PYTHON", python), - ( - "MORPHIR_EXTISM_HOST_PIN", - root + "/.dev/native-services/host-pin.json", - ), - ( - "MORPHIR_EXTISM_NATIVE", - root + "/_build/native/debug/build/morphir/morphir/morphir.exe", - ), - ( - "MORPHIR_EXTISM_RELEASE", - root + "/_build/native/release/build/morphir/morphir/morphir.exe", - ), - ]), - ) - let contents = @fs.read_file(receipt_path) - let bytes = contents.binary() - let receipt = @json.parse(contents.text()) - let pin_path = root + "/.dev/extism/python-pin.json" - let pin = @json.parse(@fs.read_file(pin_path).text()) - guard candidate(root, bundle, python) == expected && - receipt_valid(receipt, expected) && - pin == field(expected, "pin") else { - raise CommandFailure( - 1, "extism.invalid_receipt", "Real-bundle evidence is missing, incomplete, failed or belongs to different candidate bytes", - ) - } - let result : Json = { - "receiptPath": receipt_path, - "receiptSha256": @crypto.bytes_to_hex_string(@crypto.sha256(bytes)), - "pinPath": pin_path, - "receipt": receipt, - } - if json_mode { - println( - ( - { - "profile": "morphir/command-result", - "contractVersion": "0.1.0", - "command": "test:cli-extism-python", - "state": "succeeded", - "exitCode": 0, - "diagnostics": [], - "result": result, - } : Json).stringify(), - ) - } else { - println("Python Extism qualification passed: " + receipt_path) - } - } catch { - error => { - let (status, code, message) = match error { - CommandFailure(status, code, message) => (status, code, message) - _ => (1, "extism.qualification_failed", error.to_string()) - } - @stdio.stderr.write(message + "\n") - if json_mode { - println( - ( - { - "profile": "morphir/command-result", - "contractVersion": "0.1.0", - "command": "test:cli-extism-python", - "state": "failed", - "exitCode": status, - "diagnostics": [], - "error": { "code": code, "message": message }, - } : Json).stringify(), - ) - } - @sys.exit(status) - } - } -} - -///| -fn qualification_fixture() -> (Json, Json) { - let hash = "0".repeat(64) - let modules : Json = { - "extism.mjs": hash, - "supervision.mjs": hash, - "windows-job.mjs": hash, - } - let expected : Map[String, Json] = Map::from_array([ - ("platform", Json::string("darwin")), - ("architecture", Json::string("arm64")), - ("node", Json::string("v24.22.1")), - ("runtimeSha256", Json::string(hash)), - ("python", { "executable": "/python", "sha256": hash }), - ( - "host", - { "executable": "/host", "sha256": hash, "sourceRevision": "approved" }, - ), - ("hostBuild", { "sourceRevision": "approved", "cargoLockSha256": hash }), - ("bundle", { "descriptorSha256": hash, "wasmSha256": hash }), - ("sourceRevision", Json::string("candidate")), - ("sourceFiles", { "source": hash }), - ("checkDefinitionSha256", Json::string(hash)), - ("helpers", modules), - ]) - let receipt = expected.copy() - receipt.remove("helpers") - receipt["profile"] = Json::string("morphir/extism-python-qualification") - receipt["contractVersion"] = Json::string("0.1.0") - receipt["state"] = Json::string("succeeded") - let launchers : Map[String, Json] = Map([]) - let results : Array[Json] = [] - for name in ["source-native", "source-js", "packaged-native", "packaged-npm"] { - let js = name == "source-js" || name == "packaged-npm" - launchers[name] = { - "programSha256": hash, - "entrypointSha256": if js { - Json::string(hash) - } else { - Json::null() - }, - } - results.push({ - "name": name, - "programSha256": hash, - "helpers": modules, - "entrypoint": if js { - [{ "file": "/bin/morphir.js", "sha256": hash }] - } else { - [] - }, - "versions": ["3", "4"].map(major => { - ( - { - "version": major, - "parity": { "apiParity": true, "consumer": true }, - "modelSha256": hash, - "generated": ["models.py"], - } : Json) - }), - }) - } - expected["launchers"] = Json::object(launchers) - receipt["results"] = Json::array(results) - (Json::object(receipt), Json::object(expected)) -} - -///| -fn replace_field(value : Json, key : String, replacement : Json) -> Json { - guard value is Object(fields) else { panic() } - let changed = fields.copy() - changed[key] = replacement - Json::object(changed) -} - -///| -fn qualification_receipt_states() -> Unit raise { - let (receipt, expected) = qualification_fixture() - assert_true(receipt_valid(receipt, expected)) - for state in ["failed", "skipped", "pending"] { - assert_false( - receipt_valid( - replace_field(receipt, "state", Json::string(state)), - expected, - ), - ) - } - for - key in [ - "host", "hostBuild", "sourceRevision", "sourceFiles", "checkDefinitionSha256", - "platform", "architecture", "node", "runtimeSha256", "python", "bundle", - ] { - assert_false( - receipt_valid( - replace_field(receipt, key, Json::string("different candidate")), - expected, - ), - ) - } -} - -///| -fn qualification_receipt_consumers() -> Unit raise { - let (receipt, expected) = qualification_fixture() - guard field(receipt, "results") is Array(rows) else { panic() } - assert_false( - receipt_valid( - replace_field(receipt, "results", Json::array(rows[:3].to_owned())), - expected, - ), - ) - let duplicated = rows.copy() - duplicated[3] = duplicated[0] - assert_false( - receipt_valid( - replace_field(receipt, "results", Json::array(duplicated)), - expected, - ), - ) - guard field(rows[0], "versions") is Array(versions) else { panic() } - for - bad_version in [ - replace_field(versions[1], "version", Json::string("3")), - replace_field(versions[1], "parity", { - "apiParity": true, - "consumer": false, - }), - replace_field(versions[1], "parity", { - "apiParity": false, - "consumer": true, - }), - replace_field(versions[1], "modelSha256", Json::string("g".repeat(64))), - replace_field(versions[1], "generated", []), - ] { - let changed = rows.copy() - changed[0] = replace_field(rows[0], "versions", [versions[0], bad_version]) - assert_false( - receipt_valid( - replace_field(receipt, "results", Json::array(changed)), - expected, - ), - ) - } - let changed = rows.copy() - changed[1] = replace_field(rows[1], "entrypoint", [ - { "file": "/bin/morphir.js", "sha256": "other bytes" }, - ]) - assert_false( - receipt_valid( - replace_field(receipt, "results", Json::array(changed)), - expected, - ), - ) -} - -///| -test "qualification refuses failed, skipped and stale candidate evidence" { - qualification_receipt_states() -} - -///| -test "qualification requires all four launchers and both successful IR consumers" { - qualification_receipt_consumers() -} diff --git a/scripts/portable-mep-cli.mbtx b/scripts/portable-mep-cli.mbtx deleted file mode 100644 index f52fc52a1..000000000 --- a/scripts/portable-mep-cli.mbtx +++ /dev/null @@ -1,708 +0,0 @@ ----- -import { - "moonbitlang/async@0.22.7", - "moonbitlang/async@0.22.7/fs", - "moonbitlang/async@0.22.7/process", - "moonbitlang/async@0.22.7/stdio", - "moonbitlang/core/argparse", - "moonbitlang/core/env", - "moonbitlang/x@0.5.5/sys", - "moonbitlang/core/json", - "moonbitlang/core/encoding/base64", - "moonbitlang/core/encoding/utf8", - "moonbitlang/x@0.5.5/crypto", -} ----- - -// Primary-CLI acceptance for the saved core-Wasm extension route. The real -// `morphir extension` command dispatches the packaged guest through the pinned -// Node host; the independent shared kit owns the source and consumer verdict. - -///| -let host_modules : Array[String] = [ - "node-mep-cli.mjs", "node-mep-stdio.mjs", "node-mep-adapter.mjs", "node-mep-worker.mjs", - "mep-adapter.mjs", "mep-frames.mjs", "wasm-memory.mjs", "node-artifacts.mjs", "artifacts.mjs", - "assembly.mjs", "index.mjs", "dist/bridge.mjs", -] - -///| -fn sha256(bytes : Bytes) -> String { - @crypto.bytes_to_hex_string(@crypto.sha256(bytes)) -} - -///| -async fn digest(path : String) -> String { - sha256(@fs.read_file(path).binary()) -} - -///| -fn field(value : Json, name : String) -> Json { - match value { - Object(fields) => fields.get(name).unwrap_or(Json::null()) - _ => Json::null() - } -} - -///| -async fn checked( - program : String, - args : Array[String], - cwd~ : String = ".", -) -> String { - let (code, out, err) = @process.collect_output(program, args, cwd~) - guard code == 0 else { - if json_output.val { - @stdio.stderr.write(out.text() + err.text()) - raise ChildFailed( - if code > 0 { - code - } else { - 1 - }, - "Child process failed: " + program, - ) - } - @stdio.stderr.write(out.text() + err.text()) - fail( - "Portable MEP CLI acceptance failed: " + program + " " + args.join(" "), - ) - } - out.text() -} - -///| -/// One primary CLI build: the npm launcher or the native executable. -priv struct CliHost { - name : String - program : String - prefix : Array[String] - controls : Array[String] -} - -///| -priv struct Run { - code : Int32 - report : Json - stderr : String -} - -///| -/// Every invocation uses an owned TMPDIR, which must be empty afterwards: the -/// command removes its session files and the guest wrote nothing of its own. -async fn cli(host : CliHost, scratch : String, args : Array[String]) -> Run { - let tmp = scratch + "/tmp" - let (code, out, err) = @process.collect_output( - host.program, - host.prefix + ["--json"] + args + host.controls, - extra_env={ "TMPDIR": tmp }, - cwd=scratch, - ) - guard @fs.readdir(tmp, include_hidden=true).is_empty() else { - fail(host.name + " left session files: " + args.join(" ")) - } - let report = @json.parse(out.text()) catch { - _ => - fail(host.name + " returned no JSON report: " + out.text() + err.text()) - } - { code, report, stderr: err.text(), } -} - -///| -fn expect( - run : Run, - code : Int32, - error : String, - label : String, -) -> Unit raise { - let observed = match field(run.report, "error") { - String(text) => text - _ => "" - } - guard run.code == code && observed.contains(error) else { - fail( - label + - ": expected exit " + - code.to_string() + - " and error '" + - error + - "', observed " + - run.code.to_string() + - " " + - run.report.stringify() + - run.stderr, - ) - } -} - -///| -fn expect_completed_session(run : Run, label : String) -> Unit raise { - expect(run, 0, "", label) - guard field(run.report, "state") == Json("completed") && - field(field(run.report, "lifecycle"), "exitCode") == Json(0) && - field(field(run.report, "lifecycle"), "workDirectoryEmpty") == Json(true) else { - fail(label + ": incomplete lifecycle " + run.report.stringify()) - } -} - -///| -async fn write_pin( - path : String, - node : String, - host_root : String, - artifacts : String, - binding~ : String = "core-wasm-process", - wasm~ : String? = None, -) -> Json { - let modules : Map[String, Json] = Map([]) - for name in host_modules { - modules[name] = Json::string(digest(host_root + "/" + name)) - } - let pin : Json = { - "contractVersion": "0.1.0-draft.1", - "binding": binding, - "executionProfile": { - "id": "morphir/mep-bytes-v1/v1-draft", - "sha256": "6ed5e96fa9c7f7dcaa179efc88e517307463cfeddc5e2eeae854f0395e7a4ec6", - }, - "runtime": { - "name": "node", - "version": "v24.22.1", - "executable": node, - "sha256": digest(node), - }, - "host": { "root": host_root, "modules": Json::object(modules) }, - "artifact": { - "directory": artifacts, - "wasmSha256": wasm.unwrap_or(digest(artifacts + "/model.wasm")), - "manifestSha256": digest(artifacts + "/manifest.ion"), - }, - "origin": "local package:mep-extension output qualified by portable-mep-cli.mbtx", - } - @fs.write_file(path, pin.stringify() + "\n") - pin -} - -///| -/// The Boolean source comes from the shared kit, unchanged. -fn kit_source(kit : Json) -> (String, String) raise { - let source = field(kit, "source") - guard field(field(source, "package"), "name") is String(package_name) && - field(field(source, "sources"), "documents") is Array(documents) && - documents.length() == 1 && - field(documents[0], "text") is String(text) else { - fail("Shared kit has no single-document source") - } - (package_name, text) -} - -///| -/// A Morphir JSON envelope around the kit's independent plain IR. -fn json_envelope(kit : Json) -> Json { - let bytes = @utf8.encode(field(kit, "plainIr").stringify()) - let hash = sha256(bytes) - { - "envelopeVersion": "0.1.0-draft.1", - "modelId": "urn:morphir:mck:bool", - "revision": "sha256:" + hash, - "irVersion": "4.0.0", - "profile": "morphir-ir", - "payload": { - "codec": "morphir-json", - "layout": "single-document", - "decodedByteLength": bytes.length(), - "sha256": hash, - "content": @base64.encode(bytes[:]), - }, - } -} - -///| -async fn expect_artifacts( - directory : String, - expected : Json, - label : String, -) -> Unit { - guard expected is Object(files) && !files.is_empty() else { - fail("Shared kit lacks " + label) - } - for name, content in files { - guard content is String(text) && - @fs.read_file(directory + "/" + name).text() == text else { - fail(label + " differs from the shared kit: " + name) - } - } -} - -///| -/// The shared kit owns consumer source and expected stdout; the pinned -/// compiler builds the exact library returned through the CLI. -async fn consumer( - root : String, - scratch : String, - generated : String, - kit : Json, -) -> String { - guard field(field(kit, "consumer"), "files") is Object(files) && - field(field(kit, "consumer"), "expectedStdout") is String(expected) else { - fail("Missing independent consumer fixture") - } - let directory = scratch + "/consumer" - @fs.mkdir(directory) - for name, content in files { - guard content is String(text) else { fail("Invalid consumer source") } - @fs.write_file(directory + "/" + name, text) - } - @fs.write_file( - scratch + "/moon.work", - "members = " + - Json([generated, directory, root + "/pkgs/morphir-sdk"]).stringify() + - "\n", - ) - let result = checked("moon", [ - "-C", scratch, "run", "--target", "native", "consumer", - ]) - guard result == expected else { - fail("Shared consumer expected stdout differs") - } - result -} - -///| -async fn exercise( - root : String, - evidence : String, - host : CliHost, - kit : Json, - pins : Map[String, String], -) -> Json { - let scratch = evidence + "/" + host.name - @fs.mkdir(scratch) - @fs.mkdir(scratch + "/tmp") - @fs.mkdir(scratch + "/src") - let (package_name, text) = kit_source(kit) - @fs.write_file(scratch + "/src/main.mbt", text) - @fs.write_file(scratch + "/src/broken.mbt", "pub fn {") - @fs.write_file( - scratch + "/json-envelope.json", - json_envelope(kit).stringify(), - ) - let pin = pins.get("valid").unwrap() - let cases : Array[Json] = [] - let record = fn(name : String, run : Run) { - cases.push({ - "case": name, - "exitCode": run.code, - "state": field(run.report, "state"), - "error": field(run.report, "error"), - }) - } - let compile = fn(source : String, output : String, extra : Array[String]) { - [ - "extension", "compile", source, "--extension-pin", pin, "--package", package_name, - "--output", output, - ] + - extra - } - // Discovery: identity, claims and truthful runtime access. - let described = cli(host, scratch, [ - "extension", "describe", "--extension-pin", pin, - ]) - expect_completed_session(described, "describe") - guard field(field(described.report, "extension"), "id") == - Json("morphir-moonbit") && - field( - field(field(described.report, "result"), "capabilities"), - "cancellation", - ) == - Json(false) && - field(field(described.report, "access"), "wireCancellation") == Json(false) else { - fail("describe claims differ: " + described.report.stringify()) - } - record("describe", described) - // Guest-level refusals keep exact diagnostics and publish nothing. - let lossy = cli( - host, - scratch, - compile("src/main.mbt", "lossy", ["--model-format", "json"]), - ) - expect(lossy, 1, "extension.operation_unsuccessful", "lossy JSON compile") - guard field(lossy.report, "result") - .stringify() - .contains("data.lossy_conversion") && - !@fs.exists(scratch + "/lossy") else { - fail("JSON compile did not refuse lossy conversion explicitly") - } - record("compile-json-lossy-refusal", lossy) - let broken = cli(host, scratch, compile("src/broken.mbt", "broken", [])) - expect(broken, 1, "extension.operation_unsuccessful", "malformed source") - guard !@fs.exists(scratch + "/broken") else { - fail("Failed compilation published output") - } - record("compile-malformed-source", broken) - let retired = cli( - host, - scratch, - compile("src/main.mbt", "retired", [ - "--frontend-profile", "moonbit-model-bool-library-v1", - ]), - ) - expect(retired, 1, "extension.operation_unsuccessful", "retired profile") - guard field(retired.report, "result") - .stringify() - .contains("moonbit_frontend.unsupported_profile") else { - fail("Retired profile was not refused by the guest") - } - record("compile-retired-profile", retired) - // A fresh session after those failures compiles and generates end to end. - let compiled = cli(host, scratch, compile("src/main.mbt", "model", [])) - expect_completed_session(compiled, "compile") - guard field(field(field(compiled.report, "negotiated"), "tuple"), "codec") == - Json("ion-binary") && - field(field(compiled.report, "lifecycle"), "shutdown") == - Json("acknowledged") else { - fail("compile did not negotiate the Ion tuple") - } - record("compile-ion", compiled) - let generated = cli(host, scratch, [ - "extension", "generate", "model/model-envelope.json", "--extension-pin", pin, - "--output", "generated", - ]) - expect_completed_session(generated, "generate") - guard @fs.read_file(scratch + "/generated/mep/source-model.ionb").binary() == - @fs.read_file(scratch + "/model/model.ionb").binary() else { - fail("Generation did not retain the exact Ion model bytes") - } - record("generate-ion-retained", generated) - let result = consumer(root, scratch, scratch + "/generated", kit) - // Supported Morphir JSON generation matches the kit's expected artifacts. - let json = cli(host, scratch, [ - "extension", "generate", "json-envelope.json", "--extension-pin", pin, "--output", - "json-generated", - ]) - expect_completed_session(json, "generate JSON") - expect_artifacts( - scratch + "/json-generated", - field(kit, "expectedJsonArtifacts"), - "JSON generation", - ) - record("generate-json", json) - // Integrity: the guest refuses a payload whose digest does not match. - let envelope = @fs.read_file(scratch + "/model/model-envelope.json").text() - let digest_text = match - field(field(@json.parse(envelope), "payload"), "sha256") { - String(text) => text - _ => fail("Missing payload digest") - } - @fs.write_file( - scratch + "/tampered.json", - envelope.replace(old=digest_text, new="0".repeat(64)), - ) - let tampered = cli(host, scratch, [ - "extension", "generate", "tampered.json", "--extension-pin", pin, "--output", - "tampered", - ]) - guard tampered.code == 1 && !@fs.exists(scratch + "/tampered") else { - fail("Tampered model was not refused: " + tampered.report.stringify()) - } - record("generate-digest-refusal", tampered) - // Local refusals happen before any process starts. - @fs.write_file( - scratch + "/mixed.json", - envelope.replace( - old="\"profile\":\"morphir-model\"", - new="\"profile\":\"morphir-ir\"", - ), - ) - let mixed = cli(host, scratch, [ - "extension", "generate", "mixed.json", "--extension-pin", pin, "--output", "mixed", - ]) - expect(mixed, 2, "extension.unsupported_model_tuple", "mixed tuple") - record("generate-unsupported-tuple", mixed) - let exists = cli(host, scratch, compile("src/main.mbt", "model", [])) - expect(exists, 2, "extension.output_exists", "existing output") - record("existing-output-refusal", exists) - for - (name, error) in [ - ("wrong-wasm", "extension.identity_mismatch"), - ("component", "extension.unsupported_binding"), - ] { - let refused = cli(host, scratch, [ - "extension", - "describe", - "--extension-pin", - pins.get(name).unwrap(), - ]) - expect(refused, 2, error, name) - record("pin-" + name, refused) - } - @fs.write_file(scratch + "/cancel", "") - let cancelled = cli(host, scratch, [ - "extension", "describe", "--extension-pin", pin, "--cancel-file", "cancel", - ]) - expect(cancelled, 130, "execution.cancelled", "cancel file") - record("cancelled", cancelled) - let deadline = cli(host, scratch, [ - "extension", "describe", "--extension-pin", pin, "--timeout", "1", - ]) - expect(deadline, 124, "execution.deadline", "deadline") - record("deadline", deadline) - { - "host": host.name, - "cases": Json::array(cases), - "consumerResult": result, - "modelSha256": digest(scratch + "/model/model.ionb"), - "librarySha256": digest(scratch + "/generated/library.mbt"), - } -} - -///| -async fn execute() -> Json { - guard @env.get_env_var("MORPHIR_MEP_KIT") is Some(kit_path) && - @env.get_env_var("MORPHIR_MEP_KIT_SHA256") is Some(kit_digest) else { - fail( - "Set MORPHIR_MEP_KIT and MORPHIR_MEP_KIT_SHA256 to the shared guest kit", - ) - } - let kit_path = @fs.realpath(kit_path) - guard digest(kit_path) == kit_digest else { - fail("Shared kit does not match its SHA-256 pin") - } - let kit = @json.parse(@fs.read_file(kit_path).text()) - let root = @fs.realpath(".") - let artifacts = @fs.realpath( - @env.get_env_var("MORPHIR_MEP_ARTIFACTS").unwrap_or(".dev/portable-mep") + - "/extension", - ) - let evidence = @env.get_env_var("MORPHIR_MEP_CLI_EVIDENCE").unwrap_or( - ".dev/portable-mep-cli", - ) - guard !@fs.exists(evidence) else { - fail("Choose a new CLI evidence directory to preserve earlier results") - } - let compiler = checked("moon", ["version", "--all"]) - guard compiler.contains("0.10.18+6c83a7f62") else { - fail("Pinned MoonBit 0.10.18 is required") - } - let node = @fs.realpath( - checked("node", ["-p", "process.execPath"]).trim().to_owned(), - ) - guard checked(node, ["--version"]).trim() == "v24.22.1" else { - fail("Node 24.22.1 is required") - } - // Build every host byte first; the pin is taken over the final outputs. - ignore( - checked("bun", ["run", "--cwd", "pkgs/morphir-host/embedding", "build"]), - ) - ignore(checked("bun", ["run", "--cwd", "apps/morphir", "build"])) - ignore( - checked("moon", ["build", "apps/morphir", "--target", "native", "--release"]), - ) - @fs.mkdir(evidence, recursive=true) - let evidence = @fs.realpath(evidence) - let host_root = root + "/pkgs/morphir-host/embedding" - let pin = write_pin(evidence + "/pin.json", node, host_root, artifacts) - ignore( - write_pin( - evidence + "/pin-wrong-wasm.json", - node, - host_root, - artifacts, - wasm=Some("0".repeat(64)), - ), - ) - ignore( - write_pin( - evidence + "/pin-component.json", - node, - host_root, - artifacts, - binding="component-model", - ), - ) - let pins : Map[String, String] = { - "valid": evidence + "/pin.json", - "wrong-wasm": evidence + "/pin-wrong-wasm.json", - "component": evidence + "/pin-component.json", - } - let launcher = root + "/apps/morphir/bin/morphir.js" - let native = root + "/_build/native/release/build/morphir/morphir/morphir.exe" - let helper = root + "/apps/morphir/build-provider/extension.mjs" - let identities : Json = { - "launcher": digest(launcher), - "compiledCli": digest(root + "/apps/morphir/dist/morphir.js"), - "nativeCli": digest(native), - "helper": digest(helper), - "supervision": digest(root + "/apps/morphir/build-provider/supervision.mjs"), - "identity": digest(root + "/apps/morphir/build-provider/identity.mjs"), - } - let hosts = [ - { name: "js", program: node, prefix: [launcher], controls: [], }, - { - name: "native", - program: native, - prefix: [], - controls: ["--extension-helper", helper, "--node", node], - }, - ] - let results : Array[Json] = [] - for host in hosts { - results.push(exercise(root, evidence, host, kit, pins)) - } - // The same pinned bytes produced the same model and library on both hosts. - guard field(results[0], "modelSha256") == field(results[1], "modelSha256") && - field(results[0], "librarySha256") == field(results[1], "librarySha256") else { - fail("JS and native CLI hosts returned different results") - } - guard write_pin(evidence + "/pin-after.json", node, host_root, artifacts) == - pin && - digest(kit_path) == kit_digest else { - fail("Pinned inputs changed during qualification") - } - @fs.remove(evidence + "/pin-after.json") - let receipt : Json = { - "scope": "primary MoonBit CLI explicit-fixture extension route; installed bundle selection is separate", - "route": "core-wasm-process: saved core Wasm guest in the pinned Node process host; not a Component Model binding", - "sourceRevision": checked("git", ["rev-parse", "HEAD"]).trim().to_owned(), - "sourceDirty": checked("git", ["status", "--porcelain"]).trim() != "", - "compiler": compiler, - "kitSha256": kit_digest, - "pin": pin, - "cli": identities, - "hosts": Json::array(results), - } - @fs.write_file( - evidence + "/receipt.ion", - "portable_mep_cli::" + receipt.stringify() + "\n", - ) - (if json_output.val { @stdio.stderr } else { @stdio.stdout }).write( - "Primary CLI extension route passed on JS and native hosts with the shared consumer" + - "\n", - ) - receipt -} - -///| -let json_output : Ref[Bool] = Ref(false) - -///| -suberror CommandFailure { - ChildFailed(Int32, String) -} - -///| -fn legacy_command() -> @argparse.Command { - @argparse.Command( - "portable-mep-cli.mbtx", - about="Run the saved MEP guest through the primary MoonBit CLI extension command", - ) -} - -///| -fn json_command() -> @argparse.Command { - @argparse.Command( - "portable-mep-cli.mbtx", - about="Run the saved MEP guest through the primary MoonBit CLI extension command. Leading --json selects JSON mode. JSON emits one compact morphir/command-result 0.1.0 value plus LF on stdout after completion; progress goes to stderr. Success has result and diagnostics; failure has error.code/message and exitCode matching process status. Expected refusals and legacy child-failure branches exit 1; error.message retains the child status. Default abort presentation is unchanged. Result fields scope, route, sourceRevision, sourceDirty, compiler, kitSha256, pin, cli and hosts describe completed primary CLI acceptance. A leading -- protects a literal --json in legacy argv.", - disable_help_flag=true, - disable_help_subcommand=true, - subcommands=[ - @argparse.Command( - "help", - disable_help_flag=true, - disable_help_subcommand=true, - flags=[@argparse.FlagArg("help", short='h')], - ), - ], - flags=[ - @argparse.FlagArg("json", about="Emit the completed command result"), - @argparse.FlagArg( - "help", - short='h', - about="Return render_help without effects", - ), - ], - ) -} - -///| -fn wants_json(argv : ArrayView[String]) -> Bool { - // Only the leading control is reserved. The library owns its syntax; all - // other legacy argv stays untouched, including literal --json after --. - let prefix = argv[:if argv.is_empty() { 0 } else { 1 }] - let matches = @argparse.Command( - "portable-mep-cli.mbtx", - disable_help_flag=true, - disable_help_subcommand=true, - flags=[@argparse.FlagArg("json", required=true)], - ).parse(argv=prefix) catch { - _ => return false - } - matches.flags.get_or_default("json", false) -} - -///| -fn success(result : Json) -> Json { - { - "profile": "morphir/command-result", - "contractVersion": "0.1.0", - "command": "portable.mep.cli", - "state": "succeeded", - "exitCode": 0, - "result": result, - "diagnostics": [], - } -} - -///| -async fn main { - let argv = @env.args()[1:] - json_output.val = wants_json(argv) - if !json_output.val { - ignore( - @argparse.Command( - "portable-mep-cli.mbtx", - about="Run the saved MEP guest through the primary MoonBit CLI extension command", - ).parse(argv=@env.args()[1:], env=Map([])), - ) catch { - error => fail(error.to_string()) - } - ignore(execute()) - return - } - let result : Json = try { - let matches = json_command().parse(argv~) - if matches.flags.get_or_default("help", false) || - matches.subcommand is Some(_) { - { - "render_help": legacy_command().render_help(), - "json_help": json_command().render_help(), - } - } else { - execute() - } - } catch { - error => { - let (code, message, kind) = match error { - ChildFailed(code, message) => - ( - 1, - message + " (child exit " + code.to_string() + ")", - "child.failed", - ) - _ => (1, error.to_string(), "command.failed") - } - let failure : Json = { - "profile": "morphir/command-result", - "contractVersion": "0.1.0", - "command": "portable.mep.cli", - "state": "failed", - "exitCode": code, - "error": { "code": kind, "message": message }, - "diagnostics": [], - } - @stdio.stdout.write(failure.stringify() + "\n") - @sys.exit(code) - return - } - } - @stdio.stdout.write(success(result).stringify() + "\n") -} diff --git a/tests/wasm-host/README.md b/tests/wasm-host/README.md index 435e69cf5..8612784e3 100644 --- a/tests/wasm-host/README.md +++ b/tests/wasm-host/README.md @@ -147,7 +147,7 @@ Browser and native component-runtime candidates are optional for that first rout | Production JS embedding in Node/Chromium | Direct components refused | The suite passes actual component bytes to `createEmbeddedToolchain().request({operation:'run', ...})` and requires `WebAssembly.CompileError`. | | Wasmoon public native facade, pin 5490d6b | Historical values pass; resource/callback gaps | This current run is not-run for native. Do not apply the historical result to another checkout/version/platform. | | Canonical Extism public loader, Extism 1.30.0 / Wasmtime 43.0.2 | Components refused during module parsing | The same fixture component is validated and runs in the reference host. A core control reaches the separate missing Extism guest-info export. This does not qualify the core fixture as an Extism guest. | -| Primary MoonBit CLI | Component binding not integrated; saved core-Wasm route dispatched | `morphir extension` dispatches only the pinned `core-wasm-process` binding, the saved core-Wasm guest in the Node process host, and refuses component bindings. `mise run test:portable-mep-cli` qualifies it on the JS and native CLI builds. See `apps/morphir/docs/extension.md`. | +| Primary MoonBit CLI | Component binding not integrated; core-Wasm guests run on the worker host | `morphir extension` runs core-Wasm guests with the `morphir-bytes-v1` or `extism-kernel-v1` ABI on its own worker host and refuses components. `mise run test:portable-mep-cli` qualifies the saved portable guest from the native and npm CLIs. See `apps/morphir/docs/extension.md`. | | Workbench/Proton application paths | Not qualified for components by this suite | The Chromium fixture is a harness, not the shipped desktop/web application's load/invoke path. | The shared canonical host is inspected independently at finos/morphir diff --git a/tools/portable-command-json.test.ts b/tools/portable-command-json.test.ts index 4520b53d6..34f374fbb 100644 --- a/tools/portable-command-json.test.ts +++ b/tools/portable-command-json.test.ts @@ -6,7 +6,7 @@ import { dirname, join, resolve, sep } from 'node:path'; // This is a command-output regression, not a runtime, MEP, codec or MCK // qualification. Its children are deliberately synthetic native MoonBit programs. const names = ['codec-adapter-mck', 'package-codec-adapter', 'package-mep-extension', - 'portable-mep-check', 'portable-mep-cli', 'portable-mep-mck'] as const; + 'portable-mep-check', 'portable-mep-mck'] as const; type Name = typeof names[number]; const hostModules = ['node-mep-cli.mjs', 'node-mep-stdio.mjs', 'node-mep-adapter.mjs', 'node-mep-worker.mjs', 'mep-adapter.mjs', 'mep-frames.mjs', 'wasm-memory.mjs', 'node-artifacts.mjs', 'artifacts.mjs', @@ -244,7 +244,7 @@ async function regression() { PATH: bin, MOON_HOME: 'fixture preserved moon home 雪', PORTABLE_JOURNAL: journal, PORTABLE_CORE: 'fixture core --json 雪', MORPHIR_CODEC_ARTIFACTS: artifactsPath, MORPHIR_CODEC_EVIDENCE: join(cwd, 'codec-evidence'), MORPHIR_MEP_ARTIFACTS: artifactsPath, MORPHIR_MEP_EVIDENCE: join(cwd, 'local-evidence'), - MORPHIR_MEP_MCK_EVIDENCE: join(cwd, 'mck-evidence'), MORPHIR_MEP_CLI_EVIDENCE: join(cwd, 'cli-evidence'), + MORPHIR_MEP_MCK_EVIDENCE: join(cwd, 'mck-evidence'), MORPHIR_MCK_BIN: runner, MORPHIR_MCK_SHA256: fixtureHash, MORPHIR_MEP_KIT: kitPath, MORPHIR_MEP_KIT_SHA256: sha(readFileSync(kitPath)), MORPHIR_WORKBENCH_KIT: workbench, }; @@ -309,8 +309,8 @@ async function regression() { assert.equal(readFileSync(join(result.result.destination, 'recipe.txt'), 'utf8'), 'fixture recipe\n'); assert.deepEqual(launches.map(entry => entry.program), ['moon', 'moon', 'moon', 'bun', 'node']); } else { - const ion = name === 'portable-mep-cli' ? 'receipt.ion' : name === 'portable-mep-check' ? 'portable-receipt.ion' : 'qualification.ion'; - const directory = name === 'portable-mep-cli' ? 'cli-evidence' : name === 'portable-mep-check' ? 'local-evidence' : name === 'portable-mep-mck' ? 'mck-evidence' : 'codec-evidence'; + const ion = name === 'portable-mep-check' ? 'portable-receipt.ion' : 'qualification.ion'; + const directory = name === 'portable-mep-check' ? 'local-evidence' : name === 'portable-mep-mck' ? 'mck-evidence' : 'codec-evidence'; const text = readFileSync(join(success.cwd, directory, ion), 'utf8'); assert.deepEqual(JSON.parse(text.slice(text.indexOf('::') + 2)), result.result); } @@ -322,20 +322,6 @@ async function regression() { const acceptance = launches.find(entry => entry.program === 'acceptance')!; assert.ok(acceptance); assert.ok(acceptance.args[1] === success.cwd); } - if (name === 'portable-mep-cli') { - const calls = launches.filter(entry => entry.args.includes('extension')); - assert.equal(calls.length, 28); assert.ok(calls.every(entry => entry.args[0] === '--json' || entry.args[1] === '--json')); - for (const entry of calls) { - assert.ok(entry.program === 'node' || entry.program === 'morphir'); - const backend = entry.program === 'node' ? 'js' : 'native'; - const evidence = success.env['MORPHIR_MEP_CLI_EVIDENCE']!; - assert.equal(entry.cwd, join(evidence, backend)); - // The wrapper forwards this literal string; the OS normalizes cwd - // separately. Keep exact environment bytes, including mixed slashes. - assert.equal(entry.tmpdir, `${evidence}/${backend}/tmp`); - } - assert.ok(result.result.hosts.every((host: { cases: unknown[] }) => host.cases.length === 14)); - } for (const code of [23, 130]) { const failure = fixture(); failure.env['PORTABLE_FAIL_PROGRAM'] = 'moon'; failure.env['PORTABLE_FAIL_CODE'] = String(code); const failed = run(name, ['--json'], failure); @@ -363,8 +349,8 @@ async function regression() { rmSync(join(destination, 'core-graph.json'), { force: true }); mkdirSync(join(destination, 'core-graph.json')); } else { - const directory = name === 'codec-adapter-mck' ? 'codec-evidence' : name === 'portable-mep-check' ? 'local-evidence' : name === 'portable-mep-cli' ? 'cli-evidence' : 'mck-evidence'; - const file = name === 'portable-mep-check' ? 'portable-receipt.ion' : name === 'portable-mep-cli' ? 'receipt.ion' : 'qualification.ion'; + const directory = name === 'codec-adapter-mck' ? 'codec-evidence' : name === 'portable-mep-check' ? 'local-evidence' : 'mck-evidence'; + const file = name === 'portable-mep-check' ? 'portable-receipt.ion' : 'qualification.ion'; writeFailure.env['PORTABLE_BLOCK_FILE'] = join(writeFailure.cwd, directory, file); } decoded(run(name, ['--json'], writeFailure), 'failed', 1); @@ -372,7 +358,6 @@ async function regression() { const refusal = fixture(); if (name.startsWith('package-')) refusal.env['PORTABLE_FAIL_PROGRAM'] = 'missing'; else if (name === 'codec-adapter-mck') delete refusal.env['MORPHIR_MCK_BIN']; - else if (name === 'portable-mep-cli') refusal.env['MORPHIR_MEP_KIT_SHA256'] = 'wrong'; else if (name === 'portable-mep-mck') refusal.env['MORPHIR_MCK_SHA256'] = 'wrong'; else mkdirSync(refusal.env['MORPHIR_MEP_EVIDENCE']!); if (!name.startsWith('package-')) { decoded(run(name, ['--json'], refusal), 'failed', 1); assert.equal(journal(refusal).length, 0); } @@ -380,14 +365,12 @@ async function regression() { decoded(run(name, ['--json'], missing), 'failed', 1); // Legacy wrappers ignored operands. Neither variadic parsing nor new JSON // dispatch may reinterpret an escaped flag or rebuild a foreign tail. - if (name !== 'portable-mep-cli') { - const defaults = fixture(); - const legacy = run(name, ['--', '--json', '', '雪', '--', '--help', '--json=literal'], defaults); - assert.equal(legacy.code, 0, legacy.stdout + legacy.stderr); - assert.throws(() => JSON.parse(legacy.stdout)); - const escaped = fixture(); - decoded(run(name, ['--json', '--', '--json', '', '雪', '--', '--help'], escaped), 'succeeded', 0); - } + const defaults = fixture(); + const legacy = run(name, ['--', '--json', '', '雪', '--', '--help', '--json=literal'], defaults); + assert.equal(legacy.code, 0, legacy.stdout + legacy.stderr); + assert.throws(() => JSON.parse(legacy.stdout)); + const escaped = fixture(); + decoded(run(name, ['--json', '--', '--json', '', '雪', '--', '--help'], escaped), 'succeeded', 0); const legacyFailure = run(name, [], writeFailure); assert.notEqual(legacyFailure.code, 0, 'Default failure retains its backend presentation'); const baseline = target === 'native' ? process.env['PORTABLE_JSON_BASELINE'] : undefined; @@ -405,9 +388,9 @@ async function regression() { if (entry.isDirectory()) snapshot(file); else snapshots.set(file, readFileSync(file)); } } - for (const key of ['MORPHIR_CODEC_EVIDENCE', 'MORPHIR_MEP_EVIDENCE', 'MORPHIR_MEP_MCK_EVIDENCE', 'MORPHIR_MEP_CLI_EVIDENCE']) snapshot(f.env[key]!); + for (const key of ['MORPHIR_CODEC_EVIDENCE', 'MORPHIR_MEP_EVIDENCE', 'MORPHIR_MEP_MCK_EVIDENCE']) snapshot(f.env[key]!); if (name.startsWith('package-')) for (const file of ['core-graph.json', 'recipe.txt']) snapshots.set(join(f.env['MORPHIR_CODEC_ARTIFACTS']!, file), readFileSync(join(f.env['MORPHIR_CODEC_ARTIFACTS']!, file))); - for (const key of ['MORPHIR_CODEC_EVIDENCE', 'MORPHIR_MEP_EVIDENCE', 'MORPHIR_MEP_MCK_EVIDENCE', 'MORPHIR_MEP_CLI_EVIDENCE']) rmSync(f.env[key]!, { recursive: true, force: true }); + for (const key of ['MORPHIR_CODEC_EVIDENCE', 'MORPHIR_MEP_EVIDENCE', 'MORPHIR_MEP_MCK_EVIDENCE']) rmSync(f.env[key]!, { recursive: true, force: true }); const original = artifacts.get(name)!; artifacts.set(name, join(baseline, name + '.exe')); const old = run(name, [], f); @@ -420,10 +403,6 @@ async function regression() { assert.equal(baselineFailure.code, legacyFailure.code); assert.equal(baselineFailure.signal, legacyFailure.signal); artifacts.set(name, original); - if (name === 'portable-mep-cli') { - const now = run(name, ['--help'], f); artifacts.set(name, join(baseline, name + '.exe')); - assert.deepEqual(run(name, ['--help'], f), now); artifacts.set(name, original); - } } console.log(`${name}: completed result, help, refusals, child 23/130, channels and legacy operands passed`); } From f9ef5b651566d983eb153f292157bc3752188130 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:51:56 -0500 Subject: [PATCH 02/10] ci: add a manual extension qualification workflow Add .github/workflows/extension-qualification.yml, started only by workflow_dispatch, to run scripts/extension-qualification.mbtx on one runner and keep its receipts. It tests the command and its JSON help first. No pull request, merge queue, push or schedule starts it. Apply the build-master review: run npm through Node, unpack the MCK runner with the system tar on Windows, give Windows process workers the variables they need, check that the Node probe finds Node on the npm CLI path, and bound each download. Tick tasks 7.1 to 7.4. --- .github/workflows/extension-qualification.yml | 83 +++++++++++++++++++ .../scripts/extension-qualification.ts | 70 ++++++++++------ .../host-extensions-as-workers/tasks.md | 21 +++-- scripts/extension-qualification.mbtx | 35 +++++++- 4 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/extension-qualification.yml diff --git a/.github/workflows/extension-qualification.yml b/.github/workflows/extension-qualification.yml new file mode 100644 index 000000000..39dc3fbce --- /dev/null +++ b/.github/workflows/extension-qualification.yml @@ -0,0 +1,83 @@ +name: Extension qualification + +# Manual qualification of the published extensions and the SDK conformance +# fixture guest on the CLI's worker host: scripts/extension-qualification.mbtx, +# the suites of group 7 of the OpenSpec change host-extensions-as-workers. It +# downloads published releases (each checked against its SHA-256 pin) and builds +# the CLIs and several guests, so only workflow_dispatch starts it: no pull +# request, merge queue, push or schedule. Run it once per platform for a +# release candidate, for example the Windows x64 run of task 7.5, and record the +# receipts on the qualification bead. +on: + workflow_dispatch: + inputs: + runner: + description: Runner label of the platform to qualify + type: choice + options: [windows-latest, ubuntu-24.04, ubuntu-24.04-arm, macos-15] + default: windows-latest + suite: + description: Qualification suite + type: choice + options: [all, elm, python, moonbit-native, moonbit-portable, conformance] + default: all + +permissions: + contents: read + +concurrency: + group: extension-qualification-${{ github.ref }}-${{ inputs.runner }} + cancel-in-progress: false + +jobs: + qualify: + name: Extension qualification (${{ inputs.runner }}, ${{ inputs.suite }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: 120 + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup-moonbit + with: + tools: http:moonbit bun uv + - uses: actions/setup-node@v7 + with: + node-version: '24.22.1' + - name: Resolve pinned MoonBit packages + run: moon update + # The command's own tests and its JSON help, before any build or download. + - name: Test the qualification command + shell: bash + run: | + set -euo pipefail + moon test scripts/extension-qualification.mbtx + mise exec -- moonx scripts/extension-qualification.mbtx --help --json + # The script builds the CLIs and guests, downloads the pinned artifacts + # into new directories and writes one receipt for each suite. Its JSON + # result goes to stdout; build and driver output goes to stderr. + - name: Qualify the extensions on the worker host + shell: bash + env: + SUITE: ${{ inputs.suite }} + run: | + set -euo pipefail + mkdir -p .dev/extension-qualification + status=0 + # mise exec puts the mise tools (bun, uv) on PATH on every platform. + mise exec -- moonx scripts/extension-qualification.mbtx "$SUITE" --json \ + --evidence .dev/extension-qualification/evidence \ + > .dev/extension-qualification/result.json || status=$? + cat .dev/extension-qualification/result.json + exit "$status" + - name: Retain the receipts + if: always() + uses: actions/upload-artifact@v7 + with: + # yamllint disable-line rule:line-length + name: extension-qualification-${{ runner.os }}-${{ runner.arch }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} + path: | + .dev/extension-qualification/result.json + .dev/extension-qualification/evidence/*.json + include-hidden-files: true + if-no-files-found: error + # The qualification bead keeps a copy of every receipt. + retention-days: 30 diff --git a/apps/morphir/scripts/extension-qualification.ts b/apps/morphir/scripts/extension-qualification.ts index adf41cab3..8b20e3e17 100644 --- a/apps/morphir/scripts/extension-qualification.ts +++ b/apps/morphir/scripts/extension-qualification.ts @@ -71,7 +71,8 @@ function fetchPinned(url: string, filename: string, expected: Sha256): string { const target = join(fresh, filename); log(`download ${url}`); const curl = spawnSync('curl', ['--fail', '--silent', '--show-error', '--location', '--proto', '=https', - '--output', target, url], { stdio: ['ignore', 'inherit', 'inherit'] }); + '--proto-redir', '=https', '--retry', '3', '--connect-timeout', '30', '--max-time', '900', + '--max-filesize', String(512 * 1024 * 1024), '--output', target, url], { stdio: ['ignore', 'inherit', 'inherit'] }); assert.equal(curl.status, 0, `download failed: ${url}`); const actual = sha256(target); if (actual !== expected) { @@ -98,7 +99,10 @@ function mckRunner(platform: Platform): { runner: string; archiveSha256: Sha256 const unpacked = join(dirname(archive), 'unpacked'); if (!existsSync(unpacked)) { const fresh = mkdtempSync(join(dirname(archive), 'unpack-')); - const tar = spawnSync('tar', ['-xf', archive, '-C', fresh], { stdio: ['ignore', 'inherit', 'inherit'] }); + // Windows: the system bsdtar reads .zip; a GNU tar earlier on PATH (Git Bash) does not. + const tarProgram = process.platform === 'win32' + ? join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'tar.exe') : 'tar'; + const tar = spawnSync(tarProgram, ['-xf', archive, '-C', fresh], { stdio: ['ignore', 'inherit', 'inherit'] }); assert.equal(tar.status, 0, `cannot unpack ${archive}`); renameSync(fresh, unpacked); } @@ -143,7 +147,11 @@ interface Cli { function baseEnvironment(home: string, path: string[]): NodeJS.ProcessEnv { if (process.platform === 'win32') { const root = process.env.SystemRoot ?? 'C:\\Windows'; - return { SystemRoot: root, PATH: [...path, join(root, 'System32')].join(delimiter), TEMP: home, TMP: home, USERPROFILE: home }; + return { + SystemRoot: root, SystemDrive: process.env.SystemDrive ?? 'C:', ComSpec: join(root, 'System32', 'cmd.exe'), + PATHEXT: process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD', PATH: [...path, join(root, 'System32')].join(delimiter), + TEMP: home, TMP: home, USERPROFILE: home, APPDATA: home, LOCALAPPDATA: home, + }; } return { PATH: [...path, '/usr/bin', '/bin'].join(delimiter), HOME: home }; } @@ -151,7 +159,7 @@ function baseEnvironment(home: string, path: string[]): NodeJS.ProcessEnv { /** Whether `node` can be found on the PATH of an environment. */ function nodeVisible(env: NodeJS.ProcessEnv): boolean { const probe = process.platform === 'win32' - ? spawnSync('where.exe', ['node'], { env, encoding: 'utf8' }) + ? spawnSync('where.exe', ['node.exe'], { env, encoding: 'utf8' }) : spawnSync('/bin/sh', ['-c', 'command -v node'], { env, encoding: 'utf8' }); return probe.status === 0; } @@ -336,6 +344,9 @@ function elmSuite(context: Context) { if (cli.name === 'native') { ledger.step({ ...base, cli: cli.name, operation: 'environment-without-node' }, () => { assert.equal(nodeVisible(cli.env), false, `node is on the native run's PATH ${cli.env.PATH}`); + // The same probe finds Node on the npm CLI's PATH, so it can see Node. + const npm = context.clis.find(other => other.name === 'npm'); + assert.ok(!npm || nodeVisible(npm.env), 'the probe does not find Node even on the npm CLI PATH'); return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; }); } @@ -352,10 +363,9 @@ function elmSuite(context: Context) { finding: 'Elm 0.4.1 requires package.exposedModules (cli2/mep/protocol.ts); the MEP Smithy model makes it optional and the host, which conforms, does not send it; follow-up bead morphir-ukvn.5.64.36.16 (finos/morphir-elm)', }); } - if (compiled.native && compiled.npm) { - ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => { - assert.deepEqual(compiled.npm, compiled.native); - }); + const { native: nativeOutputs, npm: npmOutputs } = compiled; + if (nativeOutputs && npmOutputs) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(npmOutputs, nativeOutputs)); } } @@ -376,7 +386,8 @@ function pythonSuite(context: Context) { const directory = mkdtempSync(join(context.scratch, `python-${version}-${cli.name}-`)); cpSync(join(fixtures, 'extism-python/v1/acme'), join(directory, 'src/acme'), { recursive: true }); cpSync(join(fixtures, 'extism-python/v1/single'), join(directory, 'single'), { recursive: true }); - results[cli.name] = {}; + const mine: Record> = {}; + results[cli.name] = mine; ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-python', version, 'wasm'); assert.equal(result.worker.runtime.abi, 'extism-kernel-v1'); @@ -389,7 +400,7 @@ function pythonSuite(context: Context) { const report = command(cli, ['extension', 'compile', 'single/rules.py', '--package', 'acme/rules', '--ir-version', ir, '--output', `single-v${ir}`, '--extension-pin', pin], directory, 0); assert.equal(JSON.parse(readFileSync(join(directory, `single-v${ir}/morphir-ir.json`), 'utf8')).formatVersion, Number(ir)); - results[cli.name][`single-v${ir}`] = treeDigests(join(directory, `single-v${ir}`)); + mine[`single-v${ir}`] = treeDigests(join(directory, `single-v${ir}`)); return { outputs: outputs(join(directory, `single-v${ir}`), report.result.output.files) }; }); } @@ -411,7 +422,7 @@ function pythonSuite(context: Context) { '--output', model, '--extension-pin', pin], directory, 0); assert.equal(report.result.protocolVersion, '0.1'); assert.equal(JSON.parse(readFileSync(join(directory, model, 'morphir-ir.json'), 'utf8')).formatVersion, Number(ir)); - results[cli.name][model] = treeDigests(join(directory, model)); + mine[model] = treeDigests(join(directory, model)); return { outputs: outputs(join(directory, model), report.result.output.files) }; }); if (!compiled) continue; @@ -419,7 +430,7 @@ function pythonSuite(context: Context) { // MEP 0.1 generate takes the IR version from formatVersion and sends no options.irVersion. const report = command(cli, ['extension', 'generate', join(model, 'morphir-ir.json'), '--output', generated, '--extension-pin', pin], directory, 0); - results[cli.name][generated] = treeDigests(join(directory, generated)); + mine[generated] = treeDigests(join(directory, generated)); return { outputs: outputs(join(directory, generated), report.result.output.files) }; }); if (!generatedOk) continue; @@ -444,10 +455,11 @@ function pythonSuite(context: Context) { }); } } - if (results.native && results.npm) { + const { native: nativeOutputs, npm: npmOutputs } = results; + if (nativeOutputs && npmOutputs) { ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => { - assert.deepEqual(results.npm, results.native); - return { detail: `${Object.keys(results.native).length} output directories` }; + assert.deepEqual(npmOutputs, nativeOutputs); + return { detail: `${Object.keys(nativeOutputs).length} output directories` }; }); } } @@ -472,7 +484,8 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { mkdirSync(join(directory, 'src')); writeFileSync(join(directory, 'src/main.mbt'), document.text); writeFileSync(join(directory, 'json-envelope.json'), JSON.stringify(jsonEnvelope(shared.value.plainIr))); - results[cli.name] = {}; + const mine: Record> = {}; + results[cli.name] = mine; ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-moonbit', '0.1.0', flavour === 'native' ? 'process' : 'wasm'); if (flavour === 'portable') assert.equal(result.worker.runtime.abi, 'morphir-bytes-v1'); @@ -480,6 +493,8 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { if (cli.name === 'native' && flavour === 'native') { ledger.step({ ...base, cli: cli.name, operation: 'environment-without-node' }, () => { assert.equal(nodeVisible(cli.env), false); + const npm = context.clis.find(other => other.name === 'npm'); + assert.ok(!npm || nodeVisible(npm.env), 'the probe does not find Node even on the npm CLI PATH'); return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; }); } @@ -488,7 +503,7 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { '--extension-pin', pin], directory, 0); assert.equal(report.result.protocolVersion, '0.2.0-draft.1'); assert.equal(report.result.negotiated.tuple.codec, 'ion-binary'); - results[cli.name].model = treeDigests(join(directory, 'model')); + mine.model = treeDigests(join(directory, 'model')); return { outputs: outputs(join(directory, 'model'), report.result.output.files) }; }); if (compiled) { @@ -496,7 +511,7 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { const report = command(cli, ['extension', 'generate', 'model/model-envelope.json', '--output', 'generated', '--extension-pin', pin], directory, 0); assert.deepEqual(readFileSync(join(directory, 'generated/mep/source-model.ionb')), readFileSync(join(directory, 'model/model.ionb'))); - results[cli.name].generated = treeDigests(join(directory, 'generated')); + mine.generated = treeDigests(join(directory, 'generated')); return { outputs: outputs(join(directory, 'generated'), report.result.output.files) }; }); if (generated) { @@ -515,8 +530,9 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { return { outputs: outputs(join(directory, 'json-generated'), report.result.output.files) }; }); } - if (results.native && results.npm) { - ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(results.npm, results.native)); + const { native: nativeOutputs, npm: npmOutputs } = results; + if (nativeOutputs && npmOutputs) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(npmOutputs, nativeOutputs)); } mckGuestRun(context, flavour, artifact, shared, base); } @@ -620,7 +636,8 @@ function conformanceSuite(context: Context) { writeFileSync(join(directory, 'src/rules.flags'), '# fixture\nready = true\nblockedNow = false\n'); writeFileSync(join(directory, 'src/extra.flags'), 'enabled = true\n'); writeFileSync(join(directory, 'src/ignored.txt'), 'not a claimed source file\n'); - results[cli.name] = {}; + const mine: Record> = {}; + results[cli.name] = mine; ledger.step({ ...base, cli: cli.name, operation: 'describe-classify' }, () => { const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-sdk-fixture', '0.1.0', 'wasm'); assert.equal(result.worker.runtime.abi, 'extism-kernel-v1'); @@ -638,7 +655,7 @@ function conformanceSuite(context: Context) { const ir = JSON.parse(readFileSync(join(directory, 'model/morphir-ir.json'), 'utf8')); const modules = ir.distribution[3].modules.map((module: any) => module[0]); assert.deepEqual(modules, [[['extra']], [['rules']]], 'the .txt file is not a claimed source'); - results[cli.name].model = treeDigests(join(directory, 'model')); + mine.model = treeDigests(join(directory, 'model')); return { detail: host.message, outputs: outputs(join(directory, 'model'), report.result.output.files) }; }); ledger.step({ ...base, cli: cli.name, operation: 'generate-and-recompile' }, () => { @@ -647,7 +664,7 @@ function conformanceSuite(context: Context) { assert.equal(readFileSync(join(directory, 'generated/rules.flags'), 'utf8'), 'ready = true\nblocked_now = false\n'); command(cli, ['extension', 'compile', 'generated', '--package', 'acme/flags', '--output', 'again', '--extension-pin', pin], directory, 0); assert.deepEqual(readFileSync(join(directory, 'again/morphir-ir.json')), readFileSync(join(directory, 'model/morphir-ir.json'))); - results[cli.name].generated = treeDigests(join(directory, 'generated')); + mine.generated = treeDigests(join(directory, 'generated')); return { outputs: outputs(join(directory, 'generated'), report.result.output.files) }; }); ledger.step({ ...base, cli: cli.name, operation: 'guest-diagnostics' }, () => { @@ -659,8 +676,9 @@ function conformanceSuite(context: Context) { assert.equal(existsSync(join(directory, 'broken')), false); }); } - if (results.native && results.npm) { - ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(results.npm, results.native)); + const { native: nativeOutputs, npm: npmOutputs } = results; + if (nativeOutputs && npmOutputs) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(npmOutputs, nativeOutputs)); } } @@ -670,7 +688,7 @@ export function wasmImports(bytes: Buffer): string[] { let offset = 8; const leb = () => { let result = 0, shift = 0, byte = 0; - do { byte = bytes[offset++]; result += (byte & 0x7f) * 2 ** shift; shift += 7; } while (byte & 0x80); + do { byte = bytes[offset++]!; result += (byte & 0x7f) * 2 ** shift; shift += 7; } while (byte & 0x80); return result; }; const text = () => { const length = leb(); const value = bytes.subarray(offset, offset + length).toString('utf8'); offset += length; return value; }; diff --git a/openspec/changes/host-extensions-as-workers/tasks.md b/openspec/changes/host-extensions-as-workers/tasks.md index 684170b07..ac72f6e58 100644 --- a/openspec/changes/host-extensions-as-workers/tasks.md +++ b/openspec/changes/host-extensions-as-workers/tasks.md @@ -113,17 +113,24 @@ ## 7. Qualify the published extensions on the worker host (morphir-ukvn.5.64.36.7) -- [ ] 7.1 Qualify the Elm extension `v0.4.1` executables from the native and npm CLI on macOS arm64 and Linux x64; - verify compile of the Elm fixture and the absence of Node from the native run's environment -- [ ] 7.2 Rewrite `test:cli-extism-python` for `0.2.0` pins and qualify Python `0.4.1` (single document and the +- [x] 7.1 Qualify the Elm extension `v0.4.1` executables from the native and npm CLI on macOS arm64 and Linux x64; + verify compile of the Elm fixture and the absence of Node from the native run's environment. Describe and the + no-Node check pass; compile is a recorded failure of the guest, which requires `package.exposedModules` that MEP + makes optional (follow-up bead `morphir-ukvn.5.64.36.16`, finos/morphir-elm); no host change +- [x] 7.2 Rewrite `test:cli-extism-python` for `0.2.0` pins and qualify Python `0.4.1` (single document and the multi-document refusal) and `0.4.2` (IR 3 and 4 compile and generate, consumer check) from both CLIs; verify the receipts -- [ ] 7.3 Rewrite `test:portable-mep-cli` and `test:cli-native-prerequisites` for the worker host and qualify the - MoonBit native and portable guests with the shared MCK kit; verify the kit's expected output -- [ ] 7.4 Add a conformance fixture guest built with `morphir-extension-sdk` at a pinned revision, importing WASI and +- [x] 7.3 Rewrite `test:portable-mep-cli` and `test:cli-native-prerequisites` for the worker host and qualify the + MoonBit native and portable guests with the shared MCK kit; verify the kit's expected output. The kit's source, + consumer and JSON artifacts pass from both CLIs; the shared kit run fails at case `compile-ion` because the kit + asks for the retired profile `moonbit-model-bool-library-v1` (follow-up bead `morphir-ukvn.5.64.36.17`, + finos/morphir) +- [x] 7.4 Add a conformance fixture guest built with `morphir-extension-sdk` at a pinned revision, importing WASI and the Morphir host functions, stored with its source and build receipt under `apps/morphir/fixtures`; run the shared MEP conformance kit against it and against the MoonBit guest from both CLIs; verify the "Conformance, not a list of - extensions" scenarios with no host change made for that guest + extensions" scenarios with no host change made for that guest. No shared kit profile fits the fixture guest, so + it passes a local conformance check (`test:extension-conformance`); the shared-kit rerun is pending bead + `morphir-ukvn.5.64.36.15` - [ ] 7.5 Run one Windows x64 qualification of tasks 7.1 to 7.4 after a build-master review; verify the receipts and add a `Tested-on:` trailer - [ ] 7.6 Record every receipt with its exact artifact digests as a comment on this bead; verify each receipt names diff --git a/scripts/extension-qualification.mbtx b/scripts/extension-qualification.mbtx index 55263a0f9..7b5e5a2a4 100644 --- a/scripts/extension-qualification.mbtx +++ b/scripts/extension-qualification.mbtx @@ -178,6 +178,29 @@ fn summary( } } +///| +/// The npm command-line script that ships with the Node on PATH: +/// /node_modules/npm on Windows, /lib/node_modules/npm +/// elsewhere. +async fn npm_cli() -> String { + let (code, out, err) = @process.collect_output("node", ["-e", npm_cli_probe]) + let path = out.text().trim().to_owned() + guard code == 0 && path != "" else { + raise StepFailed( + 1, + "cannot find npm beside node: " + err.text().trim().to_owned(), + ) + } + path +} + +///| +let npm_cli_probe : String = + #|const {dirname, join} = require('node:path'); const {existsSync} = require('node:fs'); + #|const prefix = dirname(process.execPath); + #|const found = [join(prefix, 'node_modules/npm/bin/npm-cli.js'), join(prefix, '../lib/node_modules/npm/bin/npm-cli.js')].find(existsSync); + #|if (!found) process.exit(1); console.log(found); + ///| /// Python 3.12 or later for the consumer check, from uv when it is installed. async fn find_python() -> String? { @@ -214,8 +237,16 @@ async fn execute( @fs.rmdir(npm, recursive=true) } @fs.mkdir(npm, recursive=true) - step("npm", [ - "pack", "--ignore-scripts", "--silent", "--pack-destination", npm, "./apps/morphir", + // npm runs through Node: on Windows npm is npm.cmd, which a process call + // that adds .exe cannot start. + step("node", [ + npm_cli(), + "pack", + "--ignore-scripts", + "--silent", + "--pack-destination", + npm, + "./apps/morphir", ]) step("tar", ["-xzf", "\{npm}/morphir-morphir-0.1.0.tgz", "-C", npm]) let moonbit = suites.contains(MoonbitNative) || From ee88c7e79955061822d045324203f48f65b23dc8 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:42:43 -0500 Subject: [PATCH 03/10] ci: run the extension qualification step through a MoonBit helper Move the step's logic (make the directories, save the JSON result, write it to the log, keep the exit status) out of the workflow shell block into scripts/extension-qualification-run.mbtx, which has --json and --self-test. The workflow now runs one command per step. --- .github/workflows/extension-qualification.yml | 35 +- apps/morphir/docs/extension.md | 6 +- scripts/extension-qualification-run.mbtx | 308 ++++++++++++++++++ 3 files changed, 329 insertions(+), 20 deletions(-) create mode 100644 scripts/extension-qualification-run.mbtx diff --git a/.github/workflows/extension-qualification.yml b/.github/workflows/extension-qualification.yml index 39dc3fbce..e88a05e3c 100644 --- a/.github/workflows/extension-qualification.yml +++ b/.github/workflows/extension-qualification.yml @@ -44,30 +44,27 @@ jobs: node-version: '24.22.1' - name: Resolve pinned MoonBit packages run: moon update - # The command's own tests and its JSON help, before any build or download. + # The commands' own tests and JSON help, before any build or download. - name: Test the qualification command - shell: bash - run: | - set -euo pipefail - moon test scripts/extension-qualification.mbtx - mise exec -- moonx scripts/extension-qualification.mbtx --help --json - # The script builds the CLIs and guests, downloads the pinned artifacts - # into new directories and writes one receipt for each suite. Its JSON - # result goes to stdout; build and driver output goes to stderr. + run: moon test scripts/extension-qualification.mbtx + - name: Check the qualification command's JSON help + run: mise exec -- moonx scripts/extension-qualification.mbtx --help --json + - name: Self-test the qualification step + run: mise exec -- moonx scripts/extension-qualification-run.mbtx --self-test --json + # scripts/extension-qualification-run.mbtx builds the CLIs and guests + # through scripts/extension-qualification.mbtx, which downloads the pinned + # artifacts into new directories and writes one receipt for each suite. It + # saves the JSON result for the artifact, writes it to the log and keeps + # the qualification's exit status. mise exec puts bun and uv on PATH. - name: Qualify the extensions on the worker host + # One command; bash reads $SUITE from the environment on every runner. shell: bash env: SUITE: ${{ inputs.suite }} - run: | - set -euo pipefail - mkdir -p .dev/extension-qualification - status=0 - # mise exec puts the mise tools (bun, uv) on PATH on every platform. - mise exec -- moonx scripts/extension-qualification.mbtx "$SUITE" --json \ - --evidence .dev/extension-qualification/evidence \ - > .dev/extension-qualification/result.json || status=$? - cat .dev/extension-qualification/result.json - exit "$status" + run: >- + mise exec -- moonx scripts/extension-qualification-run.mbtx "$SUITE" + --evidence .dev/extension-qualification/evidence + --result .dev/extension-qualification/result.json - name: Retain the receipts if: always() uses: actions/upload-artifact@v7 diff --git a/apps/morphir/docs/extension.md b/apps/morphir/docs/extension.md index 2b7d29ebc..28b11a4b5 100644 --- a/apps/morphir/docs/extension.md +++ b/apps/morphir/docs/extension.md @@ -484,7 +484,11 @@ names its own extension. The `conformance` suite is therefore a local check. A generic kit profile is bead `morphir-ukvn.5.64.36.15`. These tasks are manual. No pull request or merge-queue job runs them, because -they download published releases and build several guests. +they download published releases and build several guests. The workflow +`.github/workflows/extension-qualification.yml`, started only by +`workflow_dispatch`, runs one suite on one runner through +`scripts/extension-qualification-run.mbtx`, which saves the JSON result for the +artifact and keeps the qualification's exit status. ## Controlled local repositories diff --git a/scripts/extension-qualification-run.mbtx b/scripts/extension-qualification-run.mbtx new file mode 100644 index 000000000..acffeb080 --- /dev/null +++ b/scripts/extension-qualification-run.mbtx @@ -0,0 +1,308 @@ +---- +import { + "moonbitlang/core/argparse", + "moonbitlang/core/env", + "moonbitlang/core/json", + "moonbitlang/async@0.22.7", + "moonbitlang/async@0.22.7/fs", + "moonbitlang/async@0.22.7/process", + "moonbitlang/async@0.22.7/stdio", + "moonbitlang/x@0.5.5/sys", +} +---- + +// The step of .github/workflows/extension-qualification.yml: it runs +// scripts/extension-qualification.mbtx for one suite in JSON mode, saves the +// JSON result in a file for the artifact, writes it to stdout for the job log +// and keeps the qualification's exit status. Workflow shell blocks hold no +// orchestration (.github/AGENTS.md), so this logic lives here. + +///| +let about : String = + #|Run scripts/extension-qualification.mbtx SUITE --json --evidence DIR (through moonx), save its JSON result in the file named by --result and write that result to stdout. The qualification's build and driver output goes to stderr as it runs. The parent directories of DIR and of the result file are made first. + #| + #|Without --json, stdout is the qualification's own morphir/command-result, and the exit status is the qualification's status. With --json, stdout is one morphir/command-result 0.1.0 of this command, extension-qualification-run: success has result {suite, resultPath, qualification}, where qualification is the saved result; a failed qualification has error {code: qualification_failed, message} and the qualification's exit status; a result that is not one JSON value has error code result_invalid and exit 1. Invalid arguments exit 2 with error code invalid_arguments. --self-test checks the argument rules and the capture of a child's stdout and status, with moon as the child, and runs no qualification. --help prints this help. + +///| +fn command() -> @argparse.Command { + @argparse.Command( + "extension-qualification-run", + about~, + disable_help_flag=true, + disable_help_subcommand=true, + positionals=[ + @argparse.PositionArg( + "SUITE", + about="The suite for scripts/extension-qualification.mbtx", + ), + ], + flags=[ + @argparse.FlagArg("json", about="Write one command-result on stdout"), + @argparse.FlagArg( + "self-test", + about="Check the argument rules and the capture; run no qualification", + ), + @argparse.FlagArg("help", short='h', about="Print the help; run nothing"), + ], + options=[ + @argparse.OptionArg("evidence", about="Evidence directory"), + @argparse.OptionArg("result", about="File for the saved JSON result"), + ], + ) +} + +///| +/// What one run needs, after the arguments are checked. +priv struct Request { + suite : String + evidence : String + result : String +} + +///| +suberror Invalid { + Invalid(String) +} + +///| +fn one(matches : @argparse.Matches, name : String) -> String? { + match matches.values.get(name) { + Some([value, ..]) if value != "" => Some(value) + _ => None + } +} + +///| +fn request(matches : @argparse.Matches) -> Request raise Invalid { + guard one(matches, "SUITE") is Some(suite) else { + raise Invalid("SUITE is required") + } + guard one(matches, "evidence") is Some(evidence) else { + raise Invalid("--evidence is required") + } + guard one(matches, "result") is Some(result) else { + raise Invalid("--result is required") + } + { suite, evidence, result, } +} + +///| +/// The directory part of a path, or "" when it has none. +fn parent(path : String) -> String { + let parts = path.replace_all(old="\\", new="/").split("/").to_array() + if parts.length() <= 1 { + "" + } else { + parts[:parts.length() - 1].join("/") + } +} + +///| +async fn make_parent(path : String) -> Unit { + let directory = parent(path) + if directory != "" && !@fs.exists(directory) { + @fs.mkdir(directory, recursive=true) + } +} + +///| +/// Runs a child with its stdout saved in `path` and its stderr on stderr, and +/// returns its exit status. +async fn capture( + program : String, + args : Array[String], + path : String, +) -> Int32 { + make_parent(path) + @process.run( + program, + args, + stdout=@process.redirect_to_file(path), + stderr=@stdio.stderr, + ) +} + +///| +fn document(state : String, code : Int32, body : (String, Json)) -> String { + let fields : Map[String, Json] = { + "profile": "morphir/command-result", + "contractVersion": "0.1.0", + "command": "extension-qualification-run", + "state": state, + "exitCode": code, + "diagnostics": [], + } + fields[body.0] = body.1 + Json::object(fields).stringify() + "\n" +} + +///| +async fn refuse( + json : Bool, + code : Int32, + kind : String, + message : String, +) -> Unit { + if json { + @stdio.stdout.write( + document("failed", code, ("error", { "code": kind, "message": message })), + ) + } else { + @stdio.stderr.write("extension-qualification-run: \{message}\n") + } + @sys.exit(code) +} + +///| +async fn run(json : Bool, request : Request) -> Unit { + make_parent(request.evidence + "/receipt") + let status = capture( + "moonx", + [ + "scripts/extension-qualification.mbtx", + request.suite, + "--json", + "--evidence", + request.evidence, + ], + request.result, + ) + let text = @fs.read_file(request.result).text() + if !json { + @stdio.stdout.write(text) + if status != 0 { + @sys.exit(status) + } + return + } + let saved = @json.parse(text) catch { + _ => { + refuse( + json, + 1, + "result_invalid", + "the qualification wrote no single JSON result (status \{status})", + ) + return + } + } + if status != 0 { + refuse( + json, + status, + "qualification_failed", + "the qualification failed with status \{status}; result \{request.result}", + ) + return + } + @stdio.stdout.write( + document( + "succeeded", + 0, + ( + "result", + { + "suite": request.suite, + "resultPath": request.result, + "qualification": saved, + }, + ), + ), + ) +} + +///| +/// Checks the argument rules and the capture with `moon` as the child. +async fn self_test() -> Unit { + let parse = fn(argv : Array[String]) -> Request? { + let matches = command().parse(argv~) catch { _ => return None } + Some(request(matches)) catch { + _ => None + } + } + guard parse(["all", "--evidence", "e", "--result", "r.json"]) + is Some({ suite: "all", evidence: "e", result: "r.json", }) else { + fail("a complete request was refused") + } + for + argv in [ + ["--evidence", "e", "--result", "r.json"], + ["all", "--result", "r.json"], + ["all", "--evidence", "e"], + ["all", "--evidence", "e", "--result", "r.json", "--unknown"], + ] { + guard parse(argv) is None else { + fail("an incomplete request was accepted: " + argv.join(" ")) + } + } + guard parent("a/b/c.json") == "a/b" && + parent("c.json") == "" && + parent("a\\b.json") == "a" else { + fail("parent directory rule changed") + } + let directory = ".dev/extension-qualification-run-self-test" + if @fs.exists(directory) { + @fs.rmdir(directory, recursive=true) + } + let saved = directory + "/nested/stdout.txt" + let status = capture("moon", ["version"], saved) + guard status == 0 && @fs.read_file(saved).text().has_prefix("moon ") else { + fail("the child's stdout was not saved") + } + let failed = capture( + "moon", + ["--no-such-option-for-the-self-test"], + directory + "/failed.txt", + ) + guard failed != 0 else { fail("a failed child's status was lost") } + @fs.rmdir(directory, recursive=true) +} + +///| +async fn main { + let json = @env.args().contains("--json") + let matches = command().parse(argv=@env.args()[1:]) catch { + error => { + refuse(json, 2, "invalid_arguments", error.to_string()) + return + } + } + let json = matches.flags.get("json").unwrap_or(false) + if matches.flags.get("help").unwrap_or(false) { + if json { + @stdio.stdout.write( + document( + "succeeded", + 0, + ("result", { "help": command().render_help() }), + ), + ) + } else { + @stdio.stdout.write(command().render_help() + "\n") + } + return + } + if matches.flags.get("self-test").unwrap_or(false) { + self_test() catch { + error => { + refuse(json, 1, "self_test_failed", error.to_string()) + return + } + } + if json { + @stdio.stdout.write( + document("succeeded", 0, ("result", { "selfTest": "passed" })), + ) + } else { + @stdio.stdout.write("extension-qualification-run: self-test passed\n") + } + return + } + let request = request(matches) catch { + Invalid(message) => { + refuse(json, 2, "invalid_arguments", message) + return + } + } + run(json, request) +} From b8277b5fb50eb0a5ed366af06f5ff99274188cb0 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:52:06 -0500 Subject: [PATCH 04/10] test(cli): qualify elm-classic 0.5.1 and the Elm 0.5.0-preview.2 WASM module Add the elm-classic v0.5.1 executables, which accept a package with no exposedModules (bead morphir-ukvn.5.64.36.16), and the Elm v0.5.0-preview.2 module (morphir-bytes-v1, model line) to the elm suite. Pin both releases by SHA-256. The WASM guest needs module names under the package name, so it gets its own fixture. The Elm 0.4.1 compile stays as a recorded failure. --- apps/morphir/docs/extension.md | 5 +- .../fixtures/elm-extension/v1/README.md | 18 +++-- .../v1/package/Acme/Rules/Main.elm | 15 ++++ .../fixtures/qualification/published.json | 50 ++++++++++++ .../scripts/extension-qualification.ts | 81 ++++++++++++++++--- docs/MISE_TASKS.md | 2 +- .../host-extensions-as-workers/tasks.md | 5 +- scripts/extension-qualification.mbtx | 2 +- 8 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 apps/morphir/fixtures/elm-extension/v1/package/Acme/Rules/Main.elm diff --git a/apps/morphir/docs/extension.md b/apps/morphir/docs/extension.md index 28b11a4b5..b69a48248 100644 --- a/apps/morphir/docs/extension.md +++ b/apps/morphir/docs/extension.md @@ -443,7 +443,7 @@ installs it (an unpacked `npm pack` of `apps/morphir`). Every task accepts | Task | Suite | What it checks | | --- | --- | --- | -| `mise run test:cli-extension-elm` | `elm` | Elm extension `v0.4.1` executables: describe with the release descriptor, compile of `apps/morphir/fixtures/elm-extension/v1`, and no `node` on the PATH of the native run | +| `mise run test:cli-extension-elm` | `elm` | Elm extension executables `v0.4.1` and elm-classic `v0.5.1` (process workers): describe with the release descriptor, compile of `apps/morphir/fixtures/elm-extension/v1/src`, and no `node` on the PATH of the native run. Elm `v0.5.0-preview.2` (one WASM module, `morphir-bytes-v1`): describe, compile of `v1/package` on the model line, generate, and recompile of the generated sources to the same model | | `mise run test:cli-extism-python` | `python` | Python extension `0.4.1`: one document for IR 3 and 4, and the refusal of two documents. `0.4.2`: compile of the two-module fixture for IR 3 and 4, generate, a consumer check of the generated Python, recompile of the generated files to the same IR, and the IR version check | | `mise run test:cli-native-prerequisites` | `moonbit-native` | The native MoonBit guest as a process worker: the shared kit's source, consumer and JSON artifacts through both CLIs, and the shared MEP kit run against the guest | | `mise run test:portable-mep-cli` | `moonbit-portable` | The portable MoonBit guest (`morphir-bytes-v1`): the same checks, with the shared kit run through `morphir extension worker` of the native CLI | @@ -471,7 +471,8 @@ passes, so the record is removed. The current known failures are: - Elm extension `v0.4.1` compile. The extension requires `package.exposedModules` in `morphir.frontend.compile`; the MEP model makes it optional and the host, which conforms, does not send it. Describe passes. - Follow-up: bead `morphir-ukvn.5.64.36.16` (finos/morphir-elm). + Bead `morphir-ukvn.5.64.36.16`: elm-classic `v0.5.1` (the same JavaScript + extension, renamed) fixes it, and its compile passes. - The shared MEP kit against the MoonBit guest. The kit `spec/mep/mck/guest-moonbit-bool.json` asks for the frontend profile `moonbit-model-bool-library-v1`, which the guest retired, so the case diff --git a/apps/morphir/fixtures/elm-extension/v1/README.md b/apps/morphir/fixtures/elm-extension/v1/README.md index 5654a4612..32927c8a7 100644 --- a/apps/morphir/fixtures/elm-extension/v1/README.md +++ b/apps/morphir/fixtures/elm-extension/v1/README.md @@ -1,6 +1,14 @@ -# Elm extension qualification fixture +# Elm extension qualification fixtures -`src/Rules.elm` is one Elm module for `mise run test:cli-extension-elm`. The Elm -extension `v0.4.1` does not claim `multiDocument`, so the fixture is one -document. Its module name has one segment because the extension compiles only -module names that its package name does not prefix. +`mise run test:cli-extension-elm` compiles these with the published Elm +extensions. + +- `src/Rules.elm`, package `acme/rules`, for the executables of `v0.4.1` and + elm-classic `v0.5.1`. They do not claim `multiDocument`, so the fixture is one + document. Its module name has one segment, because these extensions compile + only module names that the package name does not prefix. +- `package/Acme/Rules/Main.elm`, package `Acme.Rules`, for the `v0.5.0-preview.2` + WASM module. That extension requires each module name to start with the + package name and to have one more segment, and the file path to match it. + +Both hold the same two functions. diff --git a/apps/morphir/fixtures/elm-extension/v1/package/Acme/Rules/Main.elm b/apps/morphir/fixtures/elm-extension/v1/package/Acme/Rules/Main.elm new file mode 100644 index 000000000..4d8289065 --- /dev/null +++ b/apps/morphir/fixtures/elm-extension/v1/package/Acme/Rules/Main.elm @@ -0,0 +1,15 @@ +module Acme.Rules.Main exposing (eligible, priority) + + +eligible : Bool -> Bool -> Bool +eligible active vip = + active && not vip + + +priority : Bool -> Int +priority vip = + if vip then + 1 + + else + 2 diff --git a/apps/morphir/fixtures/qualification/published.json b/apps/morphir/fixtures/qualification/published.json index d228c72f7..6bc1b504b 100644 --- a/apps/morphir/fixtures/qualification/published.json +++ b/apps/morphir/fixtures/qualification/published.json @@ -36,6 +36,56 @@ } } }, + "elmClassic": { + "repository": "finos/morphir-elm", + "tag": "extension/elm-classic/v0.5.1", + "version": "0.5.1", + "sourceRevision": "947a65eea45d38c4876c91a922753a24443ebf70", + "descriptor": { + "filename": "morphir-elm-classic-extension-0.5.1.bundle.release.json", + "sha256": "ccf251ff77d6a80cd5412fbc2a56b281b50fc6a4f2ebad3a9379168bee730ffe" + }, + "executables": { + "aarch64-apple-darwin": { + "filename": "morphir-elm-classic-extension-0.5.1-aarch64-apple-darwin", + "sha256": "43716cdff0975ad43ad810ca37b3a7d43a0130c7d4509b72814ef656faf2cc04" + }, + "x86_64-apple-darwin": { + "filename": "morphir-elm-classic-extension-0.5.1-x86_64-apple-darwin", + "sha256": "e6e6df4ffa4dec8cbbe666b106def83e3dff31c45210595175629c58aa9a701f" + }, + "aarch64-unknown-linux-gnu": { + "filename": "morphir-elm-classic-extension-0.5.1-aarch64-unknown-linux-gnu", + "sha256": "151d22959d4463e2f4263ec3892c92fe9994a7895e228062192511de5b7b7b85" + }, + "x86_64-unknown-linux-gnu": { + "filename": "morphir-elm-classic-extension-0.5.1-x86_64-unknown-linux-gnu", + "sha256": "9cfcaf23353e83723cd0b153ceb863a9c0470e79e344e0ab890a36fa3eba3b37" + }, + "aarch64-pc-windows-msvc": { + "filename": "morphir-elm-classic-extension-0.5.1-aarch64-pc-windows-msvc.exe", + "sha256": "e962f2d4877dddef9d063efbcd872db14a63a5166120a7f095cd25c4bc735836" + }, + "x86_64-pc-windows-msvc": { + "filename": "morphir-elm-classic-extension-0.5.1-x86_64-pc-windows-msvc.exe", + "sha256": "b21d4b6c6f4667b5bbb532cbe7428bef9f8524baaa55db0fdbfb47d0233e3de1" + } + } + }, + "elmWasm": { + "repository": "finos/morphir-elm", + "tag": "extension/elm/v0.5.0-preview.2", + "version": "0.5.0-preview.2", + "sourceRevision": "1604c1108886ef630b877888d4f9ea877bfa32f8", + "module": { + "filename": "morphir-elm-0.5.0-preview.2.wasm", + "sha256": "4d3ea176fdc4c7b19d887c91834ba4cdfb785249e72ce23132a8d04734a8ca6c" + }, + "descriptor": { + "filename": "morphir-elm-0.5.0-preview.2.release.json", + "sha256": "4bc11967398e578175a1d38c6005b3c8e949a7c2c3a5320511a60ad37b5d39eb" + } + }, "python": { "repository": "finos/morphir-python", "releases": { diff --git a/apps/morphir/scripts/extension-qualification.ts b/apps/morphir/scripts/extension-qualification.ts index 8b20e3e17..a6b16c656 100644 --- a/apps/morphir/scripts/extension-qualification.ts +++ b/apps/morphir/scripts/extension-qualification.ts @@ -320,23 +320,36 @@ interface Context { notes: Record; } -/** Elm extension v0.4.1: published native executables as process workers (task 7.1). */ +/** + * The Elm extensions (task 7.1): the native executables of `v0.4.1` and of + * elm-classic `v0.5.1` as process workers, and the `v0.5.0-preview.2` WASM + * module (`morphir-bytes-v1`, the model line). + */ function elmSuite(context: Context) { + elmProcessGuest(context, published.elm, 'morphir-elm', { + match: /Invalid morphir\.frontend\.compile parameters/, + finding: 'Elm 0.4.1 requires package.exposedModules (cli2/mep/protocol.ts); the MEP Smithy model makes it optional and the host, which conforms, does not send it; follow-up bead morphir-ukvn.5.64.36.16 (finos/morphir-elm), fixed in elm-classic 0.5.1', + }); + elmProcessGuest(context, published.elmClassic, 'morphir-elm-classic'); + elmWasmGuest(context); +} + +/** One Elm release of native executables: describe, the no-Node check and compile of the fixture. */ +function elmProcessGuest(context: Context, elm: any, id: string, known?: KnownFailure) { const { ledger, platform } = context; - const elm = published.elm; const asset = elm.executables[platform]; assert.ok(asset, `The Elm extension ${elm.version} has no executable for ${platform}`); const executable = fetchRelease(elm, asset); if (process.platform !== 'win32') chmodSync(executable, 0o755); const descriptor = fetchRelease(elm, elm.descriptor); - const pin = writePin(join(context.scratch, 'elm-pin.json'), + const pin = writePin(join(context.scratch, `${id}-${elm.version}-pin.json`), processPin(executable, platform, `${elm.repository} ${elm.tag}`, descriptor)); - const base = { extension: 'morphir-elm', version: elm.version, guestSha256: asset.sha256 as Sha256, descriptorSha256: elm.descriptor.sha256 as Sha256 }; + const base = { extension: id, version: elm.version, guestSha256: asset.sha256 as Sha256, descriptorSha256: elm.descriptor.sha256 as Sha256 }; const compiled: Record> = {}; for (const cli of context.clis) { - const directory = mkdtempSync(join(context.scratch, `elm-${cli.name}-`)); + const directory = mkdtempSync(join(context.scratch, `${id}-${cli.name}-`)); ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { - const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-elm', elm.version, 'process'); + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), id, elm.version, 'process'); assert.equal(result.worker.descriptorSha256, elm.descriptor.sha256); assert.deepEqual(result.claims.protocolVersions, ['0.1']); return { detail: `claims ${JSON.stringify(result.claims.languages)}` }; @@ -358,10 +371,7 @@ function elmSuite(context: Context) { assert.equal(ir.formatVersion, 3); compiled[cli.name] = treeDigests(join(directory, 'model')); return { outputs: outputs(join(directory, 'model'), report.result.output.files) }; - }, { - match: /Invalid morphir\.frontend\.compile parameters/, - finding: 'Elm 0.4.1 requires package.exposedModules (cli2/mep/protocol.ts); the MEP Smithy model makes it optional and the host, which conforms, does not send it; follow-up bead morphir-ukvn.5.64.36.16 (finos/morphir-elm)', - }); + }, known); } const { native: nativeOutputs, npm: npmOutputs } = compiled; if (nativeOutputs && npmOutputs) { @@ -369,6 +379,57 @@ function elmSuite(context: Context) { } } +/** + * The Elm extension as one WASM module (`morphir-bytes-v1`): describe, compile + * on the model line, generate Elm, and recompile the generated sources to the + * same model. It names modules under the package name, so its fixture is + * `Acme.Rules.Main` in the package `Acme.Rules`. + */ +function elmWasmGuest(context: Context) { + const { ledger } = context; + const elm = published.elmWasm; + const module = fetchRelease(elm, elm.module); + const descriptor = fetchRelease(elm, elm.descriptor); + const pin = writePin(join(context.scratch, `morphir-elm-${elm.version}-pin.json`), + wasmPin(module, 'morphir-bytes-v1', `${elm.repository} ${elm.tag}`, descriptor)); + const base = { extension: 'morphir-elm', version: elm.version, guestSha256: elm.module.sha256 as Sha256, descriptorSha256: elm.descriptor.sha256 as Sha256 }; + const results: Record>> = {}; + for (const cli of context.clis) { + const directory = mkdtempSync(join(context.scratch, `morphir-elm-wasm-${cli.name}-`)); + const mine: Record> = {}; + results[cli.name] = mine; + ledger.step({ ...base, cli: cli.name, operation: 'describe' }, () => { + const result = described(command(cli, ['extension', 'describe', '--extension-pin', pin], directory, 0), 'morphir-elm', elm.version, 'wasm'); + assert.equal(result.worker.runtime.abi, 'morphir-bytes-v1'); + assert.equal(result.worker.descriptorSha256, elm.descriptor.sha256); + return { detail: `operations ${result.claims.operations.join(', ')}` }; + }); + const compiled = ledger.step({ ...base, cli: cli.name, operation: 'compile' }, () => { + const report = command(cli, ['extension', 'compile', join(fixtures, 'elm-extension/v1/package'), '--package', 'Acme.Rules', + '--output', 'model', '--extension-pin', pin], directory, 0); + assert.equal(report.result.protocolVersion, '0.2.0-draft.1'); + assert.equal(report.result.negotiated.tuple.codec, 'morphir-json'); + mine.model = treeDigests(join(directory, 'model')); + return { outputs: outputs(join(directory, 'model'), report.result.output.files) }; + }); + if (!compiled) continue; + ledger.step({ ...base, cli: cli.name, operation: 'generate-and-recompile' }, () => { + const report = command(cli, ['extension', 'generate', 'model/model-envelope.json', '--output', 'generated', + '--extension-pin', pin], directory, 0); + assert.ok(existsSync(join(directory, 'generated/src/Acme/Rules/Main.elm'))); + command(cli, ['extension', 'compile', 'generated/src', '--package', 'Acme.Rules', '--output', 'again', + '--extension-pin', pin], directory, 0); + assert.deepEqual(readFileSync(join(directory, 'again/model.json')), readFileSync(join(directory, 'model/model.json'))); + mine.generated = treeDigests(join(directory, 'generated')); + return { outputs: outputs(join(directory, 'generated'), report.result.output.files) }; + }); + } + const { native: nativeOutputs, npm: npmOutputs } = results; + if (nativeOutputs && npmOutputs) { + ledger.step({ ...base, cli: 'npm', operation: 'same-output-as-native' }, () => assert.deepEqual(npmOutputs, nativeOutputs)); + } +} + /** Python extension 0.4.1 and 0.4.2: extism-kernel-v1 WASM workers (task 7.2). */ function pythonSuite(context: Context) { const { ledger } = context; diff --git a/docs/MISE_TASKS.md b/docs/MISE_TASKS.md index f90da2c0d..bd2e2e51b 100644 --- a/docs/MISE_TASKS.md +++ b/docs/MISE_TASKS.md @@ -77,7 +77,7 @@ compiler/core pair. | `mise run test:workbench-capability-execution-mck` | Opt-in exact draft.19 immutable Scheme execution traces in Node/Chromium/native debug/release; requires explicit parent runner/kit and Workbench Chromium dependencies. Actual isolated-worker acceptance is separate | All | | `mise run test:conformance` | Compare versioned independent Ion expectations, live Scheme and explicitly pinned optional Rust; retain separate coverage receipts | All | | `mise run test:cli` | Run CLI target and npm package smoke tests | All | -| `mise run test:cli-extension-elm -- --json` | Qualify the pinned Elm extension `v0.4.1` executables on the worker host from the native and npm CLIs: describe, compile of the Elm fixture, and no Node on the native run's PATH. See [qualification](../apps/morphir/docs/extension.md#qualification) | macOS, Linux; manual | +| `mise run test:cli-extension-elm -- --json` | Qualify the pinned Elm extension executables (`v0.4.1`, elm-classic `v0.5.1`) and the Elm `v0.5.0-preview.2` WASM module on the worker host from the native and npm CLIs: describe, compile of the Elm fixtures, generate (WASM), and no Node on the native run's PATH. See [qualification](../apps/morphir/docs/extension.md#qualification) | macOS, Linux; manual | | `mise run test:cli-extism-python -- --json` | Qualify the pinned Python extension `0.4.1` (one document, multi-document refusal) and `0.4.2` (IR 3 and 4 compile, generate, consumer, recompile) from the native and npm CLIs; `--python PATH` names Python 3.12 or later for the consumer | macOS, Linux; manual | | `mise run test:cli-native-prerequisites -- --json` | Qualify the native MoonBit guest as a process worker from both CLIs with the shared kit's source, consumer and JSON artifacts, and run the shared MEP kit against it | macOS, Linux; manual | | `mise run test:portable-mep-cli -- --json` | Qualify the portable MoonBit guest (`morphir-bytes-v1`) from both CLIs, and run the shared MEP kit against it through the native CLI's worker mode | macOS, Linux; manual | diff --git a/openspec/changes/host-extensions-as-workers/tasks.md b/openspec/changes/host-extensions-as-workers/tasks.md index ac72f6e58..5f2d2f83e 100644 --- a/openspec/changes/host-extensions-as-workers/tasks.md +++ b/openspec/changes/host-extensions-as-workers/tasks.md @@ -115,8 +115,9 @@ - [x] 7.1 Qualify the Elm extension `v0.4.1` executables from the native and npm CLI on macOS arm64 and Linux x64; verify compile of the Elm fixture and the absence of Node from the native run's environment. Describe and the - no-Node check pass; compile is a recorded failure of the guest, which requires `package.exposedModules` that MEP - makes optional (follow-up bead `morphir-ukvn.5.64.36.16`, finos/morphir-elm); no host change + no-Node check pass; `v0.4.1` compile is a recorded failure of the guest, which requires `package.exposedModules` + that MEP makes optional (bead `morphir-ukvn.5.64.36.16`). Its fix, elm-classic `v0.5.1`, compiles the fixture from + both CLIs, and the Elm `v0.5.0-preview.2` WASM module is qualified as a second Elm guest; no host change - [x] 7.2 Rewrite `test:cli-extism-python` for `0.2.0` pins and qualify Python `0.4.1` (single document and the multi-document refusal) and `0.4.2` (IR 3 and 4 compile and generate, consumer check) from both CLIs; verify the receipts diff --git a/scripts/extension-qualification.mbtx b/scripts/extension-qualification.mbtx index 7b5e5a2a4..ffd351d72 100644 --- a/scripts/extension-qualification.mbtx +++ b/scripts/extension-qualification.mbtx @@ -69,7 +69,7 @@ fn Suite::name(self : Suite) -> String { ///| let about : String = - #|Qualify the published extensions and the SDK conformance fixture guest on the CLI's worker host, from the native CLI and the npm CLI (an unpacked npm pack of apps/morphir). SUITE is elm (Elm extension v0.4.1 executables), python (Python extension 0.4.1 and 0.4.2), moonbit-native and moonbit-portable (the MoonBit guest as a process worker or a morphir-bytes-v1 WASM worker, with the shared MEP kit), conformance (the morphir-extension-sdk fixture guest in apps/morphir/fixtures/sdk-guest) or all. + #|Qualify the published extensions and the SDK conformance fixture guest on the CLI's worker host, from the native CLI and the npm CLI (an unpacked npm pack of apps/morphir). SUITE is elm (Elm extension executables v0.4.1 and elm-classic v0.5.1, and the Elm v0.5.0-preview.2 WASM module), python (Python extension 0.4.1 and 0.4.2), moonbit-native and moonbit-portable (the MoonBit guest as a process worker or a morphir-bytes-v1 WASM worker, with the shared MEP kit), conformance (the morphir-extension-sdk fixture guest in apps/morphir/fixtures/sdk-guest) or all. #| #|The script builds the inputs (moon build of apps/morphir and apps/morphir-extension, bun run build, npm pack, and scripts/package-mep-extension.mbtx for moonbit-portable), then runs apps/morphir/scripts/extension-qualification.ts for each suite. The driver downloads each pinned artifact of apps/morphir/fixtures/qualification/published.json into a new, empty directory and uses it only when its SHA-256 equals the pin. It writes /.json, one receipt with one row for each operation: extension, version, cli (native, npm or mck), platform, operation, result (passed, failed or known-failure, with the finding and its follow-up bead), guest SHA-256 and output digests. A known failure is an operation that fails for a recorded cause outside the host; it does not fail the suite, and it fails the suite when it passes. #| From 68d7860c188e584f7dff8e7ee0c98c2d5e28e9a1 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:12:02 -0500 Subject: [PATCH 05/10] fix(ci): report every step helper failure as JSON In --json mode, scripts/extension-qualification-run.mbtx now catches any error of the run, such as a --result path that is a directory, and writes a failed command-result with error code command_failed and exit status 1. The default mode keeps the runtime's error output. The self-test checks that an unwritable result path raises. --- scripts/extension-qualification-run.mbtx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/extension-qualification-run.mbtx b/scripts/extension-qualification-run.mbtx index acffeb080..bbefd9da3 100644 --- a/scripts/extension-qualification-run.mbtx +++ b/scripts/extension-qualification-run.mbtx @@ -21,7 +21,7 @@ import { let about : String = #|Run scripts/extension-qualification.mbtx SUITE --json --evidence DIR (through moonx), save its JSON result in the file named by --result and write that result to stdout. The qualification's build and driver output goes to stderr as it runs. The parent directories of DIR and of the result file are made first. #| - #|Without --json, stdout is the qualification's own morphir/command-result, and the exit status is the qualification's status. With --json, stdout is one morphir/command-result 0.1.0 of this command, extension-qualification-run: success has result {suite, resultPath, qualification}, where qualification is the saved result; a failed qualification has error {code: qualification_failed, message} and the qualification's exit status; a result that is not one JSON value has error code result_invalid and exit 1. Invalid arguments exit 2 with error code invalid_arguments. --self-test checks the argument rules and the capture of a child's stdout and status, with moon as the child, and runs no qualification. --help prints this help. + #|Without --json, stdout is the qualification's own morphir/command-result, and the exit status is the qualification's status. With --json, stdout is one morphir/command-result 0.1.0 of this command, extension-qualification-run: success has result {suite, resultPath, qualification}, where qualification is the saved result; a failed qualification has error {code: qualification_failed, message} and the qualification's exit status; a result that is not one JSON value has error code result_invalid and exit 1; any other failure, such as a result path that cannot be written, has error code command_failed and exit 1. Invalid arguments exit 2 with error code invalid_arguments. --self-test checks the argument rules and the capture of a child's stdout and status, with moon as the child, and runs no qualification. --help prints this help. ///| fn command() -> @argparse.Command { @@ -255,6 +255,12 @@ async fn self_test() -> Unit { directory + "/failed.txt", ) guard failed != 0 else { fail("a failed child's status was lost") } + // A result path that cannot be written raises, so that main can report + // it as a structured failure in JSON mode. + let unusable = capture("moon", ["version"], directory) catch { _ => -1 } + guard unusable == -1 else { + fail("a result path that is a directory was accepted") + } @fs.rmdir(directory, recursive=true) } @@ -304,5 +310,13 @@ async fn main { return } } - run(json, request) + if !json { + run(json, request) + return + } + // JSON mode reports every failure as a command-result; the default mode + // keeps the error presentation of the runtime. + run(json, request) catch { + error => refuse(json, 1, "command_failed", error.to_string()) + } } From 8b0ab3c51b8e98cc2f06a876c68dcbe165c93d62 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:49:41 -0500 Subject: [PATCH 06/10] fix(cli): accept an existing evidence directory in the qualification The workflow's step helper makes the evidence directory before it runs scripts/extension-qualification.mbtx, and @fs.mkdir refuses a directory that exists. Make it only when it is missing. --- scripts/extension-qualification.mbtx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/extension-qualification.mbtx b/scripts/extension-qualification.mbtx index ffd351d72..a916e02b1 100644 --- a/scripts/extension-qualification.mbtx +++ b/scripts/extension-qualification.mbtx @@ -224,7 +224,10 @@ async fn execute( python : String?, ) -> (Bool, Json) { let root = @fs.realpath(".") - @fs.mkdir(evidence, recursive=true) + // The directory may exist: the workflow's step helper makes it first. + if !@fs.exists(evidence) { + @fs.mkdir(evidence, recursive=true) + } let evidence = @fs.realpath(evidence) let built = "\{root}/_build/native/release/build" step("moon", ["build", "--target", "native", "--release", "apps/morphir"]) From 52ce3720ee8ae5a31e30edce687757ad74e73f1d Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:00:18 -0500 Subject: [PATCH 07/10] fix(cli): run the other suites when the portable guest is not packaged On Windows the embedding build of the portable guest fails before any suite runs. A packaging failure now fails only the moonbit-portable suite, with its cause in the summary, and the other suites still run. --- scripts/extension-qualification.mbtx | 70 +++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/scripts/extension-qualification.mbtx b/scripts/extension-qualification.mbtx index a916e02b1..029196635 100644 --- a/scripts/extension-qualification.mbtx +++ b/scripts/extension-qualification.mbtx @@ -73,7 +73,7 @@ let about : String = #| #|The script builds the inputs (moon build of apps/morphir and apps/morphir-extension, bun run build, npm pack, and scripts/package-mep-extension.mbtx for moonbit-portable), then runs apps/morphir/scripts/extension-qualification.ts for each suite. The driver downloads each pinned artifact of apps/morphir/fixtures/qualification/published.json into a new, empty directory and uses it only when its SHA-256 equals the pin. It writes /.json, one receipt with one row for each operation: extension, version, cli (native, npm or mck), platform, operation, result (passed, failed or known-failure, with the finding and its follow-up bead), guest SHA-256 and output digests. A known failure is an operation that fails for a recorded cause outside the host; it does not fail the suite, and it fails the suite when it passes. #| - #|Build, download and driver output goes to stderr. Without --json, stdout has one summary line for each suite. With --json, stdout is one morphir/command-result 0.1.0 value written after every receipt: result {evidence, suites: [{suite, state, receiptPath, receiptSha256, passed, knownFailures, failed}], receipts: []}. A failure has error {code, message} instead of result: qualification_failed (exit 1) when a suite failed, step_failed with the exit status of a failed build step, invalid_arguments (exit 2) for invalid arguments. --help prints this help and runs nothing; --help --json returns it as result.help. + #|Build, download and driver output goes to stderr. Without --json, stdout has one summary line for each suite. With --json, stdout is one morphir/command-result 0.1.0 value written after every receipt: result {evidence, suites: [{suite, state, receiptPath, receiptSha256, passed, knownFailures, failed}], receipts: []}. A suite whose inputs could not be built (for example the portable guest's packaging) has state failed and error instead of a receipt, and the other suites still run. A failure has error {code, message} instead of result: qualification_failed (exit 1) when a suite failed, step_failed with the exit status of a failed build step, invalid_arguments (exit 2) for invalid arguments. --help prints this help and runs nothing; --help --json returns it as result.help. ///| fn command() -> @argparse.Command { @@ -201,6 +201,35 @@ let npm_cli_probe : String = #|const found = [join(prefix, 'node_modules/npm/bin/npm-cli.js'), join(prefix, '../lib/node_modules/npm/bin/npm-cli.js')].find(existsSync); #|if (!found) process.exit(1); console.log(found); +///| +/// Packages the portable MoonBit guest into `portable`, afresh. +async fn package_portable(portable : String) -> Unit { + step("bun", [ + "install", "--frozen-lockfile", "--cwd", "pkgs/morphir-host/embedding", + ]) + // The packaging refuses an existing destination. + if @fs.exists(portable) { + @fs.rmdir(portable, recursive=true) + } + step("moonx", ["scripts/package-mep-extension.mbtx"], env={ + "MORPHIR_MEP_ARTIFACTS": portable, + }) +} + +///| +/// The summary of a suite whose inputs could not be built: failed, with no +/// receipt. +fn unbuilt(suite : Suite, message : String) -> Json { + { + "suite": suite.name(), + "state": "failed", + "error": message, + "passed": 0, + "knownFailures": 0, + "failed": 0, + } +} + ///| /// Python 3.12 or later for the consumer check, from uv when it is installed. async fn find_python() -> String? { @@ -260,17 +289,14 @@ async fn execute( ]) } let portable = "\{evidence}/portable-mep" + // A packaging failure fails only the moonbit-portable suite; the other + // suites still run. + let portable_error : Ref[String?] = Ref(None) if suites.contains(MoonbitPortable) { - step("bun", [ - "install", "--frozen-lockfile", "--cwd", "pkgs/morphir-host/embedding", - ]) - // The packaging refuses an existing destination. - if @fs.exists(portable) { - @fs.rmdir(portable, recursive=true) + package_portable(portable) catch { + StepFailed(_, message) => portable_error.val = Some(message) + error => portable_error.val = Some(error.to_string()) } - step("moonx", ["scripts/package-mep-extension.mbtx"], env={ - "MORPHIR_MEP_ARTIFACTS": portable, - }) } let python = match python { Some(path) => Some(path) @@ -280,6 +306,13 @@ async fn execute( let receipts : Array[Json] = [] let mut passed = true for suite in suites { + if suite == MoonbitPortable && portable_error.val is Some(message) { + passed = false + summaries.push( + unbuilt(suite, "the portable guest was not packaged: " + message), + ) + continue + } let receipt_path = "\{evidence}/\{suite.name()}.json" if @fs.exists(receipt_path) { @fs.remove(receipt_path) @@ -376,7 +409,11 @@ fn summary_line(item : Json) -> String { _ => "" } } - "\{text("suite")}: \{text("state")} (\{text("passed")} passed, \{text("knownFailures")} known failures, \{text("failed")} failed) \{text("receiptPath")}" + let line = "\{text("suite")}: \{text("state")} (\{text("passed")} passed, \{text("knownFailures")} known failures, \{text("failed")} failed) \{text("receiptPath")}" + match field(item, "error") { + String(error) => line + ": " + error + _ => line + } } ///| @@ -483,3 +520,14 @@ test "a summary counts the rows of each result" { "elm: passed (2 passed, 1 known failures, 0 failed) /e/elm.json", ) } + +///| +test "a suite whose inputs were not built is failed with its cause" { + let item = unbuilt(MoonbitPortable, "the portable guest was not packaged: x") + assert_eq(field(item, "state"), Json::string("failed")) + assert_eq(field(item, "suite"), Json::string("moonbit-portable")) + assert_eq( + field(item, "error"), + Json::string("the portable guest was not packaged: x"), + ) +} From e5804546639c075dc1b801280a2965fab7990e62 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:09:04 -0500 Subject: [PATCH 08/10] test(cli): keep LF line endings in the qualification fixtures A Windows checkout converted the SDK fixture's Cargo.toml to CRLF, so its digest no longer matched the build receipt. Check the qualification fixtures out with LF on every platform. --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitattributes b/.gitattributes index b23cd54e7..61797b47a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,10 @@ tools/packaged-targets-pin.json text eol=lf # Keep the complete pinned Archify package byte-exact and collapsed in code review. .agents/skills/archify/** -text linguist-vendored .agents/skills/archify/UPSTREAM.md text eol=lf linguist-vendored=false +# Qualification fixtures: their digests and the IR they compile to must not +# depend on the checkout's line endings. +apps/morphir/fixtures/sdk-guest/** text eol=lf +apps/morphir/fixtures/sdk-guest/*.wasm binary +apps/morphir/fixtures/elm-extension/** text eol=lf +apps/morphir/fixtures/extism-python/** text eol=lf +apps/morphir/fixtures/qualification/** text eol=lf From dc227dd7df6c6617a5676ae95c2b60135dd30fcd Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:59:45 -0500 Subject: [PATCH 09/10] docs(openspec): record the Windows qualification result and tick 7.6 The Windows x64 run of task 7.5 failed at output publication (bead morphir-ukvn.5.64.36.19) and could not package the portable guest (bead morphir-ukvn.5.64.36.18), so 7.5 stays open. Every receipt is recorded on bead morphir-ukvn.5.64.36.7. --- openspec/changes/host-extensions-as-workers/tasks.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openspec/changes/host-extensions-as-workers/tasks.md b/openspec/changes/host-extensions-as-workers/tasks.md index 388fb3172..5cc1e6d0c 100644 --- a/openspec/changes/host-extensions-as-workers/tasks.md +++ b/openspec/changes/host-extensions-as-workers/tasks.md @@ -133,8 +133,11 @@ it passes a local conformance check (`test:extension-conformance`); the shared-kit rerun is pending bead `morphir-ukvn.5.64.36.15` - [ ] 7.5 Run one Windows x64 qualification of tasks 7.1 to 7.4 after a build-master review; verify the receipts and - add a `Tested-on:` trailer -- [ ] 7.6 Record every receipt with its exact artifact digests as a comment on this bead; verify each receipt names + add a `Tested-on:` trailer. Run 41241517957 failed: every guest session runs, but no output is published, because the + native `_WIN32` directory rename is a stub and the Node publication creates the destination before it renames onto + it (bead `morphir-ukvn.5.64.36.19`); the portable guest is not packaged on Windows (bead `morphir-ukvn.5.64.36.18`). + No `Tested-on:` trailer until a rerun passes +- [x] 7.6 Record every receipt with its exact artifact digests as a comment on this bead; verify each receipt names its platform, CLI build and guest digest ## 8. Browser Web Worker feasibility (morphir-ukvn.5.64.36.8) From d7a0b626c495af267e7a462c33a9440032704f3d Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:45:31 -0500 Subject: [PATCH 10/10] test(cli): give native qualification runs a PATH with no Node On a host with Node in /usr/bin or /bin, the native CLI's PATH exposed node and the no-Node rows failed for the host's layout. On Unix the native PATH is now one new, empty directory: the native CLI starts its workers by path and needs no program from PATH. The check asserts that each native PATH entry is empty, that the probe finds no node there and that it finds node on the npm CLI PATH, and the receipt says so. --- .../scripts/extension-qualification.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/apps/morphir/scripts/extension-qualification.ts b/apps/morphir/scripts/extension-qualification.ts index a6b16c656..c01c995dc 100644 --- a/apps/morphir/scripts/extension-qualification.ts +++ b/apps/morphir/scripts/extension-qualification.ts @@ -139,12 +139,16 @@ interface Cli { name: CliName; command: string; prefix: string[]; - /** The environment of every run: a minimal PATH, with Node only for the npm CLI. */ + /** + * The environment of every run. On Unix the native CLI's PATH is one new, + * empty directory, so no `node` can be on it, wherever the host installs + * Node; the npm CLI's PATH is Node's directory, then `/usr/bin` and `/bin`. + */ env: NodeJS.ProcessEnv; build: { commit: string; dirty: boolean; programSha256: Sha256; entrySha256?: Sha256; node?: string }; } -function baseEnvironment(home: string, path: string[]): NodeJS.ProcessEnv { +function baseEnvironment(home: string, path: string[], isolated = false): NodeJS.ProcessEnv { if (process.platform === 'win32') { const root = process.env.SystemRoot ?? 'C:\\Windows'; return { @@ -153,7 +157,19 @@ function baseEnvironment(home: string, path: string[]): NodeJS.ProcessEnv { TEMP: home, TMP: home, USERPROFILE: home, APPDATA: home, LOCALAPPDATA: home, }; } - return { PATH: [...path, '/usr/bin', '/bin'].join(delimiter), HOME: home }; + return { PATH: (isolated ? path : [...path, '/usr/bin', '/bin']).join(delimiter), HOME: home }; +} + +/** Checks that the native PATH is isolated (Unix: only empty directories) and says what the no-Node check found. */ +function nodeCheckDetail(env: NodeJS.ProcessEnv): string { + if (process.platform !== 'win32') { + for (const entry of (env.PATH ?? '').split(delimiter)) { + assert.deepEqual(readdirSync(entry), [], `the native PATH entry ${entry} is not an empty directory`); + } + } + const entries = (env.PATH ?? '').split(delimiter).map(entry => `${entry} (${existsSync(entry) ? readdirSync(entry).length : 0} entries)`); + const probe = process.platform === 'win32' ? 'where.exe node.exe' : "/bin/sh -c 'command -v node'"; + return `native PATH: ${entries.join(delimiter)}; ${probe} finds no node there and finds node on the npm CLI PATH`; } /** Whether `node` can be found on the PATH of an environment. */ @@ -171,8 +187,11 @@ function gitBuild() { function clis(native: string, npmEntry: string, home: string): Cli[] { const build = gitBuild(); + // The native CLI needs no program from PATH: it starts its workers by path. + const emptyPath = join(home, '..', 'native-path'); + mkdirSync(emptyPath); return [ - { name: 'native', command: native, prefix: [], env: baseEnvironment(home, []), + { name: 'native', command: native, prefix: [], env: baseEnvironment(home, [emptyPath], true), build: { ...build, programSha256: sha256(native) } }, { name: 'npm', command: process.execPath, prefix: [npmEntry], env: baseEnvironment(home, [dirname(process.execPath)]), build: { ...build, programSha256: sha256(process.execPath), entrySha256: sha256(npmEntry), node: process.version } }, @@ -360,7 +379,7 @@ function elmProcessGuest(context: Context, elm: any, id: string, known?: KnownFa // The same probe finds Node on the npm CLI's PATH, so it can see Node. const npm = context.clis.find(other => other.name === 'npm'); assert.ok(!npm || nodeVisible(npm.env), 'the probe does not find Node even on the npm CLI PATH'); - return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; + return { detail: nodeCheckDetail(cli.env) }; }); } ledger.step({ ...base, cli: cli.name, operation: 'compile' }, () => { @@ -556,7 +575,7 @@ function moonbitSuite(context: Context, flavour: 'native' | 'portable') { assert.equal(nodeVisible(cli.env), false); const npm = context.clis.find(other => other.name === 'npm'); assert.ok(!npm || nodeVisible(npm.env), 'the probe does not find Node even on the npm CLI PATH'); - return { detail: `native runs use PATH=${cli.env.PATH}; node is not on it` }; + return { detail: nodeCheckDetail(cli.env) }; }); } const compiled = ledger.step({ ...base, cli: cli.name, operation: 'compile-kit-source' }, () => {