Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .beads/config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Beads issue tracking for this repository uses the shared Morphir tracker.
# The database lives in finos/morphir (refs/dolt/data), not in this repository.
# `bd bootstrap` clones it; `bd dolt pull` and `bd dolt push` keep it in sync.
sync.remote: "git+https://github.com/finos/morphir.git"
sync.branch: "beads-sync"
sync:
remote: "git+https://github.com/finos/morphir.git"
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,21 @@ morphir-python/
### Package Responsibilities

**`morphir` (core library)**
- Morphir IR (Intermediate Representation) models
- Morphir IR (Intermediate Representation) models, in two versions:
`morphir.ir.v3` (format 3.0.0; morphir-elm is normative) and `morphir.ir.v4`
(draft upstream; `morphir.ir` re-exports it). Read `formatVersion` only through
`morphir.ir.format_version`, convert only through `morphir.ir.migrate`, and keep
diagnostic codes in `morphir.ir.diagnostics`. See
[packages/morphir/README.md](packages/morphir/README.md#morphir-ir-versions) and
`openspec/changes/add-ir-v3-support/`
- Type definitions and type algebra
- Pure functional primitives
- Morphir SDK runtime (`morphir.sdk`): Basics, Char, String, List, Dict, Set, Maybe, Result, Tuple, Decimal, Int, Number
- Zero CLI or IO dependencies
- Should be usable as a standalone library

**`morphir-tools` (CLI/tools)**
- Command-line interface
- Command-line interface (`morphir-tools`, Typer): `ir inspect`, `ir convert`
- File I/O operations
- Code generation backends
- Depends on `morphir` core library
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,32 @@ uv add morphir-tools

## Quick Start

Morphir IR comes in two first-class versions: format 3.0.0 (`morphir.ir.v3`), which
morphir-elm writes today, and draft IR v4 (`morphir.ir.v4`, also re-exported as
`morphir.ir`). Read a morphir-elm file and migrate it to v4:

```python
from morphir.ir import Type, Value
from pathlib import Path

from morphir.ir.migrate import v3_to_v4
from morphir.ir.v3 import codec

# Example usage will be added as the library develops
library = codec.decode_versioned_distribution(Path("morphir-ir.json").read_text())
v4_library = v3_to_v4(library).value
```

Or from the command line:

```bash
morphir-tools ir inspect morphir-ir.json
morphir-tools ir convert morphir-ir.json --to v4 -o morphir-ir.v4.json
```

See the [morphir package README](packages/morphir/README.md#morphir-ir-versions)
for format-version handling, the draft v4 status, and migrations, and the
[`add-ir-v3-support` change](openspec/changes/add-ir-v3-support/proposal.md) for
the design.

## Morphir SDK

`morphir.sdk` is the Python runtime for the Morphir SDK, the standard library that
Expand Down
36 changes: 36 additions & 0 deletions features/ir_interchange.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Feature: IR interchange
As a Morphir tooling user
I want to inspect and convert versioned morphir-ir.json files
So that I can move models between IR format 3.0.0 and draft IR v4

Scenario: Inspect a morphir-elm format 3 file
Given the golden fixture "elm-simpleValueTree.json"
When I run "ir inspect" on it
Then the command exits with code 0
And the output contains "format: 3.0.0"
And the output contains "kind: Library"
And the output contains "package: morphir/example/app"

Scenario: Upgrade a format 3 file to draft v4 and back
Given the golden fixture "elm-simpleValueTree.json"
When I convert it to "v4"
Then the command exits with code 0
And the converted file declares formatVersion 4
When I inspect the converted file
Then the output contains "draft"
When I convert the converted file to "v3"
Then the command exits with code 0
And the converted file has the same JSON as the fixture

Scenario: Unversioned IR is rejected
Given a file with the JSON '{"distribution": []}'
When I run "ir inspect" on it
Then the command exits with code 1
And the errors contain "missing_format_version"

Scenario: Specs cannot be downgraded
Given a file with the JSON '{"formatVersion": 4, "distribution": {"Specs": {"packageName": "a"}}}'
When I convert it to "v3"
Then the command exits with code 1
And the errors contain "unsupported_v4_downgrade"
And no converted file exists
116 changes: 116 additions & 0 deletions features/steps/ir_interchange_steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Step definitions for the IR interchange CLI."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING

from behave import given, then, when
from typer.testing import CliRunner

from morphir_tools.cli import app

if TYPE_CHECKING:
from behave.runner import Context

FIXTURES = Path(__file__).resolve().parents[2] / "tests" / "fixtures" / "ir" / "v3"


def _workdir(context: Context) -> Path:
if not hasattr(context, "workdir"):
context.workdir = Path(tempfile.mkdtemp(prefix="morphir-bdd-"))
return context.workdir


def _run(context: Context, *args: str) -> None:
context.result = CliRunner().invoke(app, list(args))


@given('the golden fixture "{name}"')
def step_fixture(context: Context, name: str) -> None:
"""Select a golden format-3 fixture as input."""
context.input = FIXTURES / name
context.fixture = context.input


@given("a file with the JSON '{text}'")
def step_json_file(context: Context, text: str) -> None:
"""Write JSON text to an input file."""
path = _workdir(context) / "input.json"
path.write_text(text)
context.input = path


@when('I run "ir inspect" on it')
def step_inspect(context: Context) -> None:
"""Inspect the input file."""
_run(context, "ir", "inspect", str(context.input))


@when('I convert it to "{target}"')
def step_convert(context: Context, target: str) -> None:
"""Convert the input file to the target version."""
context.output = _workdir(context) / f"converted-{target}.json"
_run(
context,
"ir",
"convert",
str(context.input),
"--to",
target,
"-o",
str(context.output),
)


@when("I inspect the converted file")
def step_inspect_converted(context: Context) -> None:
"""Inspect the converted file."""
_run(context, "ir", "inspect", str(context.output))


@when('I convert the converted file to "{target}"')
def step_convert_converted(context: Context, target: str) -> None:
"""Convert the converted file again."""
context.input = context.output
step_convert(context, target)


@then("the command exits with code {code:d}")
def step_exit_code(context: Context, code: int) -> None:
"""Check the exit code."""
assert context.result.exit_code == code, context.result.output


@then('the output contains "{text}"')
def step_output_contains(context: Context, text: str) -> None:
"""Check standard output."""
assert text in context.result.stdout, context.result.stdout


@then('the errors contain "{text}"')
def step_errors_contain(context: Context, text: str) -> None:
"""Check standard error."""
assert text in context.result.stderr, context.result.stderr


@then("the converted file declares formatVersion {version:d}")
def step_declares_version(context: Context, version: int) -> None:
"""Check the converted file's formatVersion."""
assert json.loads(context.output.read_text())["formatVersion"] == version


@then("the converted file has the same JSON as the fixture")
def step_same_json(context: Context) -> None:
"""Check the converted file matches the fixture JSON."""
assert json.loads(context.output.read_text()) == json.loads(
context.fixture.read_text()
)


@then("no converted file exists")
def step_no_output(context: Context) -> None:
"""Check no output file was written."""
assert not context.output.exists()
2 changes: 2 additions & 0 deletions openspec/changes/add-ir-v3-support/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: html-design
created: 2026-10-27
Loading
Loading