diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cdb1f2a..a3724d25 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -366,7 +366,10 @@ jobs: - name: Refuse missing, extra or mismatched candidates run: | - tag="${{ github.ref_type == 'tag' && github.ref_name || '' }}" + tag="" + if [[ "$GITHUB_REF" == refs/tags/native-v* ]]; then + tag="$GITHUB_REF_NAME" + fi if [[ -z "$tag" ]]; then tag="native-v$(python3 -c 'import tomllib; print(tomllib.load(open("rust/crates/morphir-python-native/Cargo.toml","rb"))["package"]["version"])')" fi diff --git a/openspec/changes/add-process-mep-extension/design.html b/openspec/changes/add-process-mep-extension/design.html index 109387eb..bb76f858 100644 --- a/openspec/changes/add-process-mep-extension/design.html +++ b/openspec/changes/add-process-mep-extension/design.html @@ -22,8 +22,8 @@

add-process-mep-extension

-

OpenSpec change · Bead morphir-5hxy · 2026-10-28 · Implementation qualified; landing and component publication pending

- +

OpenSpec change · Bead morphir-5hxy · 2026-10-28 · Implementation landed; native published; extension qualification and CLI metadata correction in progress

+

Context

See proposal.md for motivation and the two spec deltas for requirements. PythonExtension already implements native Rust frontend/backend traits. The baseline published SDK 0.3.0 exposed NativeExtension::frontend_backend, open_protocol, model methods and admission helpers. Its wire request type used a numeric ID, while the canonical contract admits numeric and string IDs; adopting helpers alone is not full draft conformance. The baseline Python0.4.2 implementation declared legacy MEP0.1 only. Candidate0.5.0 uses publishedSDK 0.4.0 and opts into the exact JSON model draft after native/WASM executable qualification. finos/morphir c552dac801f23f2b2b566d29e08780723498ce36 defines exact MEP 0.2.0-draft.1 and model envelope 0.1.0-draft.1. These versions are independent of SDK 0.3.0, compiler 0.4.2, claims format and IR version.

Goals and non-goals

Add a native Rust process guest and current-draft model adapters while retaining WASM and legacy behavior. Keep language semantics shared. Initial model tuples use morphir-ir/morphir-json and supported Library IR 3.0.0 and draft 4.0.0. Rich Ion profiles, unsupported distribution kinds, additional Python syntax, cancellation/progress and performance claims are not introduced. A Python CLI stdio wrapper can follow separately; the selected CLI installation remains mise/pipx.

Decisions

RNT-1. Add a thin native Rust guest

Add a process guest crate/entry point, depending on the maintained compiler and published SDK, with no PyO3 or CPython requirement. The proposed command is:

morphir-python-extension --stdio

No-argument launches also select stdio, matching the canonical native bundler and installed process launcher. Reuse the SDK's session endpoint and dispatch. Keep model adaptation in the shared PythonExtension so native, WASM and process paths agree. Put stdio effects in the guest layer. A Python CLI wrapper would reuse the engine but retain an interpreter/launcher dependency; a frozen Python CLI would bundle an unnecessary runtime for this guest.

BoundaryCurrent releaseProposed change
MEPLegacy 0.1Retain 0.1 and add exact 0.2.0-draft.1
Model envelopeNot advertised0.1.0-draft.1, admitted JSON profile
Extension runtimeWASMWASM and native process
SDK0.3.0, numeric-only wire IDsQualified published pin covering the canonical wire contract
Semantic IRSupported v3 and draft v4 Library modelsSame semantic scope, explicit admitted tuples

SDK-1. Resolve concrete SDK wire gaps before claiming conformance

Pin a published SDK that supports canonical numeric and string request IDs and the required draft behavior. SDK 0.3.0 currently has numeric-only ExtensionRequest.id, so link and qualify an upstream SDK update if no suitable release exists. Do not vendor a sibling checkout or silently coerce wire IDs. Both WASM and process must pass the same ID-preservation vectors before the new draft is advertised. The concrete prerequisite is request ID support, issue 316, tracked as morphir-4wrx. SDK 0.4.0 is merged in PR 317 as d51daa8aea7ae09ae295ff254d72b27770c64104. All CI and the committed-source publication dry run pass. The maintainer approved publication at that exact merge. Tag crates/morphir-extension-sdk/v0.4.0 triggered successful workflow40695514380; Cargo independently downloaded the published0.4.0 crate. The compiler now pins registry dependency =0.4.0, without a sibling or Git SDK dependency. Related upstream work is MEP protocol type generation, issue 241; its existence is not evidence that the wire gap is fixed.

WIN-1. Keep the standalone Windows runtime independent of Python

Binary inspection of the b38e00e development executable found VCRUNTIME140.dll. Build only the standalone guest with -C target-feature=+crt-static; preserve the Python wheel's existing ABI configuration. The Windows producer uses MSVC dumpbin /DEPENDENTS, records exact artifact/tool/checker identities, and refuses VC or Python runtime DLL imports. Assembly requires that source-bound proof. This CRT configuration needs separate code generation from the Python wheel; it is not a compatible cache consumer.

MEP-1. Negotiate the new draft explicitly

Retain legacy 0.1 operations and exact 0.2.0-draft.1 offers. Opt in only after supported model methods and executable conformance pass. Reuse SDK model admission and session rules; method constants alone are not support. Never enable model methods under legacy negotiation. Keep optional flags false until implemented.

MOD-1. Start with the explicit JSON model profile

Implement compile_model/generate_model over the existing compile/generate core. Admit complete operation/profile/codec/envelope/IR tuples and their bilateral limits. The initial profile is morphir-ir/morphir-json with envelope 0.1.0-draft.1. Use the canonical base64 payload, exact byte length/hash and critical-field rules. After SDK admission, materialize JSON through published morphir-json 0.2.0 with finite depth/node/string budgets, preserving numeric lexemes and literal private-looking keys. Bound outgoing JSON before extending payload buffers. Reject unadvertised Ion profiles, guarantees and unsupported semantic forms. This achieves bounded new-draft support without implying rich Ion preservation or silently converting rich input.

IO-1. Treat framing and lifetime as a bounded server boundary

Use Content-Length JSON-RPC framing, byte-based lengths, an 8 KiB header ceiling and an initial 16 MiB complete-body ceiling. Advertised decoded/encoded model limits must fit those outer bounds, including envelope overhead. Enforce local bounds even if the peer advertises more. Check partial input, duplicate lengths, integer overflow, invalid UTF-8/JSON and EOF before unbounded allocation. Reserve stdout for frames and flush responses. Follow initialization, capability locks, shutdown and exit; do not respond to notifications. Host supervision owns deadlines and descendant cleanup.

RLS-1. Qualify dual-runtime distribution

Retain the portable WASM guest and publish native process executables for the advertised targets, with canonical empty arguments selecting stdio by default. Use a canonical host-readable descriptor with runtime/platform/mode/digest/claims bound per artifact. Validate the staged descriptor with pinned host tooling; do not assume that either the existing WASM-only packager or native-only bundle command can package a mixed release unchanged. Preserve historical assets. Release exact native/CLI package pairs if shared compiler changes affect them.

HST-1. Qualify hosts independently

The Rust host provides ProcessLaunch/ProcessChannel and verified-byte staging. It clears the child environment, favoring a directly executable Rust guest. Qualify a pinned conforming Rust host and test each documented MoonBit route independently. Use a pinned source build when a released host lacks the new draft, and state that prerequisite. If a route needs upstream work, record that dependency and label it unsupported until it passes. Existing MoonBit process wrappers demonstrate framing, not Python extension activation.

@@ -48,6 +48,7 @@

add-process-mep-extension

The b38e00e development candidate passed all five platform producers and process-only assembly in run 40676766327. Cached-tool identity, IR and generated-artifact digests agreed with WASM/API references. The verified CLI recipe selects separate named repositories, with uninstall before changing the active runtime. A real macOS check switched WASM/process/WASM, rejected a foreign-only descriptor and refused changed installed bytes. Integrity checks now run inside every platform producer with fresh project caches and exact expected/observed hashes; atomic replacement avoids modifying hardlinked inputs. These checks remain distinct from the pending new-draft MCK prerequisite. Replacement source 820c9d0 passed all five producers and assembly in run 40685569455. Its Windows dependency proof admits only OS DLLs, with no VC or Python runtime imports. All eleven combined release files, the five producer receipts and the Windows checker proof were independently matched to the collected assembly hashes. Runtime switching, installed-byte tampering and foreign-platform refusal passed on the assembled Linux candidate.

The current candidate supplies the previously missing platform, conformance, transfer and promotion evidence. The assessment concerns implementation assurance; it is not publication authorization or a general performance result. Per-tag qualification still gates publication at the exact approved source. No native speed claim follows from this build design.

Windows checker setup and source integrity

The full candidate run exposed Cargo/libgit2 failing on a locked Gleam snapshot path of 265 characters under the default Windows Cargo home. A short checker-only cache under RUNNER_TEMP/mck reduces the longest pinned path to 252 characters. A second diagnostic showed that transient parent Git options did not persist into the Rust submodule: inaccessible long fixtures produced false deleted/modified statuses. Preparation now configures LF and long-path handling locally in each checkout and requires clean source before and after compilation. An actual tracked-file edit is still refused and preserved. No source patch, ignored diff, changed corpus or reduced platform requirement was accepted.

Diagnostic run 40726235322 passes with clean parent/submodule state and a prepared Windows checker receipt. The manual mck-diagnostic mode builds only that checker and retains raw compiler and Git diagnostics; it cannot qualify or publish a release. Its separate concurrency group permits diagnosis while the full platform run continues. The normal workflow remains the promotion authority. The first Linux preparation of the shared checker and WASM adapter took 12m24s, followed by 1m31s for canonical execution and saved rechecks; these are assurance costs, not guest performance measurements.

Download assets and transformed-byte checks

The assembly category stages all five raw executables with checksums, a separately named process descriptor, and one deterministic gzip/tar download containing the canonical eleven-file process bundle. Archive metadata uses fixed ownership/timestamps and explicit executable permissions; every copied and archived payload must retain its original digest. Corrupt transfers, mixed source/claims/platforms, symlinks and extra files are refused without leaving partial assets. The candidate receipt and final checksum list bind the download bytes to assembly. The existing runtime-selection and source-pinned host checks consume the extracted archive, preserving the build-once contract. Actual 820c9d0 archive bytes pass local offline switching, installed-byte refusal and unsupported-platform refusal. The same manual/extension-tag workflow now requires canonical producer MCK rechecks before assembly. Final promotion copies verified raw process assets, their separate descriptor/archive, WASM assets and all producer evidence, then seals their final names. Manual runs stage this exact release set without publishing. An approved extension tag publishes it after all selected jobs pass.

Source-pinned host and timing observations

The standalone developer checker in tools/extension-qualification/ pins Rust host source d51daa8aea7ae09ae295ff254d72b27770c64104, independently of the compiler's published SDK dependency. Actual b38e00e WASM and macOS process bytes pass legacy initialization, v3/v4 type/value compile-generate-recompile, diagnostic parity, orderly close and a real timeout with child reaping. This is not executable MCK or new-draft qualification.

A release-built checker ran five fresh samples per workload on one macOS ARM64 machine. The small workload has one record and one function; the medium workload has 200 records and 100 typed conditional functions. Raw results, input files, exact artifact/checker/lock/toolchain hashes and descriptive timings are retained in .dev/qualification/timings-b38-release/. Startup includes fresh guest initialization and WASM module compilation. OS caches are not cleared; later v3/v4 operations share the session and WASM runs first. No general speed claim follows. The existing manual/release candidate consumer now owns source-pinned host checks and raw timing retention; it builds the checker once and consumes the already qualified guest bytes. The extension promotion job depends on that consumer. Estimated uncached Linux host build cost is 3 to 6 minutes; the observed local release build took 53 seconds. Actual CI timing remains to be measured.

Qualification result

All 16 implementation tasks are complete. Full candidate run 40726356123 succeeds at source bce5d859b524edf452b3031e2e2e56fc32f4da71. Later documentation, task and spec-sync edits preserve the production code and check definitions. Publication remains pending exact maintainer approval and per-tag validation.

ObligationObserved evidence
Platform and API agreementAll five process producers and WASM pass installed offline v3/v4 operation checks. Full process diagnostic records match native Python API 0.1.2 with compiler 0.5.0. Windows runtime imports pass.
Canonical current/legacy MEPSix reports each contain 91 required observations with successful saved rechecks from clean merged85cf4352 source, totaling 546. Local transfer verification preserves original paths and exact source/tool/artifact/claim identities.
Assembly and download formFive raw process targets combine into an eleven-file canonical bundle and deterministic archive. Extracted-archive switching, installed-byte tampering and foreign-platform refusal pass. An actual four-platform input set is refused.
Real hosts and timingsPinned Rust host d51daa8 passes actual WASM/process legacy lifecycle, v3/v4 operations, diagnostics, timeout and child cleanup. Ten raw samples, five per small/medium workload, retain inputs and exact toolchain/artifact identities. No general speed claim.
Promotion and retentionFinal staging has 39 assets plus SHA256SUMS. Every final checksum, six raw report/recheck pairs and all eleven archive payload/mode entries are independently checked. Historical 0.4.0/0.4.2 WASM assets remain uploaded with their original digests.

Local validation passes 2632 tests with five intentional skips, strict typing, BDD, focused evidence/assembly tests, actionlint, site checks and strict OpenSpec validation. SDK 0.4.0 is published; Python/native/extension tags have not yet been published. The OpenSpec change remains active until that operational follow-up is complete.

+

CLI publication prerequisite

Approved native tag native-v0.1.2 at f2032e3adaf72854a3907dc092e8abb757f08b4a published successfully in workflow 40758932916; all six PyPI files match the qualified release bytes. Approved CLI tag python-v0.1.1 correctly failed workflow 40764461472 before publication: core 0.1.0 documentation links and its project URL changed after publication. Direct wheel comparison proves identical runtime files, with only METADATA and RECORD differing. Bug morphir-5hxy.6 prepares core 0.1.1 followed by CLI 0.1.2. The strict published-core byte guard remains unchanged, and the failed CLI tag is preserved. Native 0.1.2 and extension 0.5.0 retain their approved source and versions. Corrected core/CLI publication awaits exact approval after qualification.

The extension-tag run also exposed a separate routing error: its native candidate verifier received extension/python/v0.5.0 and correctly refused it as a non-native tag. Bug morphir-5hxy.7 keeps that verifier and the complete wheel-set check, using the source native version for extension/manual contexts while validating explicit native tags unchanged. A regression executes the production workflow shell for manual, native, extension and invalid-native routes; the extension route fails before the fix and all routes pass afterward. Protected extension publication remains held. Corrected-source approval is required before retargeting the unpublished extension tag and repeating full qualification; published native tags remain unchanged.

Selective PR CI also exposed a stale committed host-evidence fallback after earlier documentation input changes. Bug morphir-5hxy.8 replaces it with actual qualification from clean commit 6e88b295017d346007409bdd7182e8584c91c622: the currently documented 0.4.2 bundle passes the pinned Rust-host recipe and offline round trips, and both pinned MoonBit refusal probes pass. A regression verifies the real committed cache against all documented inputs, including when CI separately runs fresh host checks.

Repository evidence and references

Related work: morphir-bj9r owns the selected CLI installation route; morphir-ukvn.5.98 tracks the earlier MoonBit extension qualification context.

diff --git a/openspec/changes/add-process-mep-extension/tasks.md b/openspec/changes/add-process-mep-extension/tasks.md index 6e1a880d..da731ca7 100644 --- a/openspec/changes/add-process-mep-extension/tasks.md +++ b/openspec/changes/add-process-mep-extension/tasks.md @@ -40,4 +40,9 @@ This change is being applied. SDK 0.4.0 is published and the separate upstream P - Final promotion stages 39 assets plus `SHA256SUMS`. Local audit verifies every final checksum, all 11 canonical process archive members, payload hashes, executable modes and all six raw MCK report/recheck pairs. A real four-platform input set is refused for missing Intel evidence. Historical WASM releases `0.4.0` and `0.4.2` remain available with their recorded digests. - Source-pinned Rust host checks and ten fresh timing samples for small/medium workloads pass on the actual candidate bytes. Raw inputs, reports and toolchain/build identities are retained in the candidate and final host-evidence archive. No general performance claim is made. MoonBit routes remain unsupported at the documented pin with checked limitations. - SDK `0.4.0` publication and the separate upstream Python JSON MCK profile are complete. The Windows checker build passes with short scoped Cargo storage and explicit parent/submodule Git settings; real source changes are still refused. No corpus change or integrity exception was accepted. -- Main specs are synced. All merges and admin bypass are approved. Python/native/extension release tags require exact maintainer approval once the merged source is concrete. Keep this change active until publication and final verification; archive afterward. Do not treat staged assets as published releases. +- Main specs are synced. All merges and admin bypass are approved. The maintainer approved native `0.1.2`, CLI `0.1.1` and extension `0.5.0` at merge `f2032e3adaf72854a3907dc092e8abb757f08b4a`. +- Native [40758932916](https://github.com/finos/morphir-python/actions/runs/40758932916) published all five wheels and the source archive. Every PyPI file matches its qualified workflow artifact and checksum exactly. Extension [40758936256](https://github.com/finos/morphir-python/actions/runs/40758936256) is qualifying the approved source. +- CLI [40764461472](https://github.com/finos/morphir-python/actions/runs/40764461472) correctly refused publication: the current core `0.1.0` wheel differs from PyPI after documentation-site metadata edits. Direct comparison confirms identical runtime sources and changes only to `METADATA` and `RECORD`. No CLI files were published. Bug `morphir-5hxy.6` prepares core `0.1.1` and CLI `0.1.2`; preserve the failed `python-v0.1.1` tag and the strict byte-identity guard. Corrected publication requires approval for the new tags and source after qualification. +- Extension run `40758936256` passed its platform MEP checks but its native candidate verifier correctly rejected the forwarded extension tag. Bug `morphir-5hxy.7` preserves the verifier and selects the source native tag for extension/manual runs; an actual workflow-shell regression fails before the fix and passes afterward, including rejection of a wrong explicit native tag. Keep extension publication held. Approval for the corrected source is required before retargeting the unpublished extension tag and repeating all gates; published native tags remain unchanged. +- Bug `morphir-5hxy.8` refreshes the stale committed host-evidence fallback exposed by selective PR CI. Fresh qualification from clean commit `6e88b295017d346007409bdd7182e8584c91c622` passes the currently documented released Rust-host recipe, offline round trips and both pinned MoonBit refusal probes. A regression checks the real committed cache against every documented input, preventing a full fresh-host CI run from hiding stale fallback evidence. +- Keep this change active until publication and final verification; archive afterward. Do not treat staged assets as published releases. diff --git a/packages/morphir-tools/README.md b/packages/morphir-tools/README.md index 5ca42248..c027c316 100644 --- a/packages/morphir-tools/README.md +++ b/packages/morphir-tools/README.md @@ -24,7 +24,7 @@ wheel. No Rust toolchain is required on supported platforms. To install the CLI and compiler API into an existing Python environment: ```bash -python -m pip install --only-binary=:all: morphir-python==0.1.0 +python -m pip install --only-binary=:all: morphir-python==0.1.2 ``` Or with uv: diff --git a/packages/morphir-tools/pyproject.toml b/packages/morphir-tools/pyproject.toml index b879f22e..ffd8ed3a 100644 --- a/packages/morphir-tools/pyproject.toml +++ b/packages/morphir-tools/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "morphir-python" -version = "0.1.1" +version = "0.1.2" description = "CLI tools and extensions for Morphir Python" readme = "README.md" license = "Apache-2.0" diff --git a/packages/morphir-tools/src/morphir_tools/__init__.py b/packages/morphir-tools/src/morphir_tools/__init__.py index 5b33636e..5510d6d3 100644 --- a/packages/morphir-tools/src/morphir_tools/__init__.py +++ b/packages/morphir-tools/src/morphir_tools/__init__.py @@ -6,8 +6,8 @@ Example: >>> import morphir_tools >>> morphir_tools.__version__ - '0.1.0' + '0.1.2' """ -__version__ = "0.1.1" +__version__ = "0.1.2" __all__ = ["__version__"] diff --git a/packages/morphir/pyproject.toml b/packages/morphir/pyproject.toml index b34bc3d5..8d16f8b8 100644 --- a/packages/morphir/pyproject.toml +++ b/packages/morphir/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "morphir" -version = "0.1.0" +version = "0.1.1" description = "Python port of Morphir - functional domain modeling for the enterprise" readme = "README.md" license = "Apache-2.0" diff --git a/packages/morphir/src/morphir/__init__.py b/packages/morphir/src/morphir/__init__.py index 8074c7d1..2f15c14b 100644 --- a/packages/morphir/src/morphir/__init__.py +++ b/packages/morphir/src/morphir/__init__.py @@ -7,8 +7,8 @@ Example: >>> import morphir >>> morphir.__version__ - '0.1.0' + '0.1.1' """ -__version__ = "0.1.0" +__version__ = "0.1.1" __all__ = ["__version__"] diff --git a/site/evidence/hosts.json b/site/evidence/hosts.json index 9532c12d..7294fe73 100644 --- a/site/evidence/hosts.json +++ b/site/evidence/hosts.json @@ -3,13 +3,13 @@ "state": "succeeded", "inputs": { "site/integrations.json": "d83a529d8d4d59c7505892f93cb7e991939e38174892cd0f11f2172c090b3578", - "rust/extension.toml": "6fe92784006cb757c9d6d1e27c1869b2ac5d4ecda4753644d9d8d202b9d7ff5d", + "rust/extension.toml": "31a882701022d95198aad254c4db61659d2b6ec138585a49c673d13a16c80272", "scripts/docs_hosts.py": "791c2e220217e8931e1c090749987a17e7787fcdbba35443d83b3e234f8b7cc6", - "scripts/extension_bundle.py": "2a2937f2274db14cfee54dd24616045f55592cefe7c99611aa73098fb44cbcd6", + "scripts/extension_bundle.py": "ca03e3d959428dfef41fb0c852eaa3d656f13133f6e4f996db77ea9952126bc3", "examples/extension.sh": "bbe0a0cf27070b888439f57a9e495eb75506b8fafbafd259b1a8512bcf1af35b", "examples/model/acme/models.py": "0f57ac6f4ffd2524373ec9ba3df079f23291ce42ca7f7700e4df50fad9d43b50", "examples/model/acme/rules.py": "cf475c980096c8645550c4c53c5e6343e3a584f2a19cba26c16cf1291ce235c2", - "docs/python-extension.md": "47969c1d2c7de272c6aa275ff0a558b1efc8778556c2796e0108c0a31ff8af4a", + "docs/python-extension.md": "326b9ee7ca856ba88b3ad67accac5fa4657444e0b7968f3eec23287d9db4459c", "docs/moonbit.md": "574f1e0129975dc10ea68882b48dda07ced36f429b2a30746c6bc724e9f119c2", "docs/compatibility.md": "3f5ef69142991edf9d30d23dce768d66cf0047f4b325c1f042166a5c2f9c29f8", "tests/fixtures/python/loans/acme/models.py": "02b453935df7a03fd8e58c88616836d8f09e78868b9c66cae572c18852f6ccf4", @@ -17,8 +17,8 @@ }, "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", "python": "3.14.7 (main, Sep 1 2026, 14:05:28) [Clang 22.1.3 ]", - "working_tree_dirty": true, - "git_head": "a55985d32c24987b1ae790ea05acd4bc0a39deb1", + "working_tree_dirty": false, + "git_head": "6e88b295017d346007409bdd7182e8584c91c622", "rust_recipe": { "recipe": "examples/extension.sh", "exit_code": 0, @@ -39,10 +39,20 @@ "frontend" ], "multiDocument": true, + "installed_integrity": { + "expected_sha256": "10acafa0f0282c5240e9620a5f2bb21629cc7580b9add528e7b77c7e54db22b5", + "tampered_sha256": "9fd562d8ee2d99ede2e7bd41bb219d755ec23a72813c5cbf81854a8d8b9d1fc3", + "exit_code": 1, + "stdout": "", + "stderr": "Operation ID: op-215cbd72-59bc-4117-880b-f2bff34e2544\nLog: /var/folders/gk/kq2l8mzd0lq64qspbc_6j80m0000gn/T/python-bundle-host-kuoivpf8/home/logs/cli/2026-10-28/20261028T212014.733Z-7424-1d00-18e2ce4909789a80.jsonl\nError: cli::extension_error\n\n \u00d7 Extension error: Failed to verify installed provider 'morphir-python':\n \u2502 digest mismatch for /private/var/folders/gk/kq2l8mzd0lq64qspbc_6j80m0000gn/\n \u2502 T/python-bundle-host-kuoivpf8/home/store/extensions/\n \u2502 sha256/10acafa0f0282c5240e9620a5f2bb21629cc7580b9add528e7b77c7e54db22b5/\n \u2502 morphir-python-binding-0.4.2.wasm: expected\n \u2502 10acafa0f0282c5240e9620a5f2bb21629cc7580b9add528e7b77c7e54db22b5, got\n \u2502 9fd562d8ee2d99ede2e7bd41bb219d755ec23a72813c5cbf81854a8d8b9d1fc3\n\n", + "fresh_project": true + }, "host": { "morphir": "0.4.0-beta.10", - "sha256": "98635ec74fbdc3c2cef1d4ae184bb1c5ed9e2960575f3d8abb4b97e0e4da363f" + "sha256": "98635ec74fbdc3c2cef1d4ae184bb1c5ed9e2960575f3d8abb4b97e0e4da363f", + "binary_sha256": "e610796e758c4f2b8925228082efd78f9b9d46c45ee22b6e393d9b76b8e807e2" }, + "qualification_script_sha256": "ca03e3d959428dfef41fb0c852eaa3d656f13133f6e4f996db77ea9952126bc3", "python_api": { "native_version": "0.1.1", "compiler_version": "0.4.2" @@ -53,6 +63,11 @@ "acme/models.py", "acme/rules.py" ], + "ir_sha256": "a8211ac125e271a60eea3c3580347ce37c1ec83fb599e15c5954b3a4eed30c6b", + "generated_sha256": { + "acme/models.py": "b1a754879944f2ffdc40873e778d9ebb57adb363e635d730975c02e263f0025f", + "acme/rules.py": "0f9b72e04c0d857d555dcafd76763a07a20cabcb88c8f55fcfb81cc1436183a5" + }, "agrees_with_python_api": true }, "v4": { @@ -60,6 +75,11 @@ "acme/models.py", "acme/rules.py" ], + "ir_sha256": "dd41e858e3b0bf769a4884e5722e09029adf6613fc95d4552fa01986e80a315c", + "generated_sha256": { + "acme/models.py": "b1a754879944f2ffdc40873e778d9ebb57adb363e635d730975c02e263f0025f", + "acme/rules.py": "0f9b72e04c0d857d555dcafd76763a07a20cabcb88c8f55fcfb81cc1436183a5" + }, "agrees_with_python_api": true } } diff --git a/tests/conftest.py b/tests/conftest.py index f95a2880..637153ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,4 +6,4 @@ @pytest.fixture def sample_version() -> str: """Provide the expected version string for tests.""" - return "0.1.0" + return "0.1.1" diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index f48b3a16..9e8b14c4 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -14,7 +14,7 @@ def test_version_is_defined(self) -> None: def test_version_matches_expected(self) -> None: """The version should match the expected value.""" - assert morphir_tools.__version__ == "0.1.1" + assert morphir_tools.__version__ == "0.1.2" def test_installed_distribution_name(self) -> None: """The tools import belongs to the morphir-python distribution.""" diff --git a/tests/unit/test_docs_hosts.py b/tests/unit/test_docs_hosts.py index d7322e5e..a41d2497 100644 --- a/tests/unit/test_docs_hosts.py +++ b/tests/unit/test_docs_hosts.py @@ -5,12 +5,17 @@ import pytest -from scripts.docs_hosts import INPUTS, input_hashes, verify_evidence +from scripts.docs_hosts import INPUTS, ROOT, input_hashes, verify_evidence if TYPE_CHECKING: from pathlib import Path +def test_committed_host_evidence_matches_documented_inputs() -> None: + """Keep the selective CI fallback usable after documentation changes.""" + verify_evidence(json.loads((ROOT / "site/evidence/hosts.json").read_text())) + + def test_edited_recipe_invalidates_previously_successful_evidence( tmp_path: Path, ) -> None: diff --git a/tests/unit/test_release_candidates.py b/tests/unit/test_release_candidates.py index 1466e739..c8c255b4 100644 --- a/tests/unit/test_release_candidates.py +++ b/tests/unit/test_release_candidates.py @@ -4,6 +4,11 @@ import hashlib import json +import os +import shlex +import shutil +import subprocess +import textwrap from typing import TYPE_CHECKING import pytest @@ -125,3 +130,57 @@ def test_the_command_writes_a_receipt(tmp_path: Path) -> None: pytest.skip("fixture names the current native version") r.main(["native", str(tmp_path), "--tag", f"native-v{version}"]) assert len((tmp_path / "SHA256SUMS").read_text().splitlines()) == 6 + + +@pytest.mark.skipif( + os.name == "nt" or shutil.which("bash") is None, + reason="The native-candidates workflow shell runs on Ubuntu", +) +@pytest.mark.parametrize( + ("tag", "accepted"), + [ + (None, True), + ("native-v{native}", True), + ("extension/python/v{extension}", True), + ("native-v9.9.9", False), + ], +) +def test_workflow_verifies_native_candidates_for_each_tag_route( + tmp_path: Path, tag: str | None, accepted: bool +) -> None: + """Execute the production shell and its strict validator with real ref inputs.""" + versions = r.source_versions() + native_set(tmp_path, versions["native"]) + workflow = (r.ROOT / ".github/workflows/release.yml").read_text() + step = workflow.split( + " - name: Refuse missing, extra or mismatched candidates\n", 1 + )[1] + script = textwrap.dedent( + step.split(" run: |\n", 1)[1].split("\n - name:", 1)[0] + ) + ref_name = tag.format(**versions) if tag else "main" + # GitHub expands this expression before handing a step to Bash. + script = script.replace( + "${{ github.ref_type == 'tag' && github.ref_name || '' }}", + ref_name if tag else "", + ).replace("dist/candidates", shlex.quote(str(tmp_path))) + result = subprocess.run( + ["bash", "-e", "-c", script], + cwd=r.ROOT, + env=os.environ + | { + "GITHUB_REF": f"refs/tags/{ref_name}" if tag else "refs/heads/main", + "GITHUB_REF_NAME": ref_name, + "GITHUB_REF_TYPE": "tag" if tag else "branch", + }, + capture_output=True, + text=True, + timeout=30, + check=False, + ) + assert (result.returncode == 0) == accepted, result.stderr + receipt = tmp_path / "SHA256SUMS" + if accepted: + assert len(receipt.read_text().splitlines()) == 6 + else: + assert not receipt.exists() diff --git a/uv.lock b/uv.lock index 3881afac..d63533cb 100644 --- a/uv.lock +++ b/uv.lock @@ -196,12 +196,12 @@ wheels = [ [[package]] name = "morphir" -version = "0.1.0" +version = "0.1.1" source = { editable = "packages/morphir" } [[package]] name = "morphir-python" -version = "0.1.1" +version = "0.1.2" source = { editable = "packages/morphir-tools" } dependencies = [ { name = "morphir" },