From 11705db176999aaf67e95dba1d1a88b4e857886e Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Tue, 27 Oct 2026 01:52:15 -0500 Subject: [PATCH] chore(openspec): archive add-ir-v3-support and publish the IR specs Archive the implemented add-ir-v3-support change and merge its deltas into openspec/specs (ir/format-version, ir/migrate, ir/v3, ir/v4, tools/ir-interchange), so follow-up changes for morphir-ukvn.8 can modify these requirements. Point the root README and AGENTS.md at the new paths. Refs: morphir-ukvn.8, morphir-ukvn.8.16 --- AGENTS.md | 2 +- README.md | 7 +- .../.openspec.yaml | 0 .../2026-10-27-add-ir-v3-support}/design.html | 10 +-- .../2026-10-27-add-ir-v3-support}/proposal.md | 0 .../specs/ir/format-version/spec.md | 0 .../specs/ir/migrate/spec.md | 0 .../specs/ir/v3/spec.md | 0 .../specs/ir/v4/spec.md | 0 .../specs/tools/ir-interchange/spec.md | 0 .../2026-10-27-add-ir-v3-support}/tasks.md | 0 openspec/specs/ir/format-version/spec.md | 57 +++++++++++++++ openspec/specs/ir/migrate/spec.md | 69 +++++++++++++++++++ openspec/specs/ir/v3/spec.md | 54 +++++++++++++++ openspec/specs/ir/v4/spec.md | 53 ++++++++++++++ openspec/specs/tools/ir-interchange/spec.md | 50 ++++++++++++++ 16 files changed, 293 insertions(+), 9 deletions(-) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/.openspec.yaml (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/design.html (98%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/proposal.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/specs/ir/format-version/spec.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/specs/ir/migrate/spec.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/specs/ir/v3/spec.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/specs/ir/v4/spec.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/specs/tools/ir-interchange/spec.md (100%) rename openspec/changes/{add-ir-v3-support => archive/2026-10-27-add-ir-v3-support}/tasks.md (100%) create mode 100644 openspec/specs/ir/format-version/spec.md create mode 100644 openspec/specs/ir/migrate/spec.md create mode 100644 openspec/specs/ir/v3/spec.md create mode 100644 openspec/specs/ir/v4/spec.md create mode 100644 openspec/specs/tools/ir-interchange/spec.md diff --git a/AGENTS.md b/AGENTS.md index cb8cbfed..41c629b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,7 +28,7 @@ morphir-python/ `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/` + the `ir/*` and `tools/ir-interchange` specs in `openspec/specs/` - 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 diff --git a/README.md b/README.md index fd804627..150030f6 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,10 @@ 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. +for format-version handling, the draft v4 status, and migrations, the +[IR specs](openspec/specs/) for the requirements, and the archived +[`add-ir-v3-support` change](openspec/changes/archive/2026-10-27-add-ir-v3-support/proposal.md) +for the design. ## Morphir SDK diff --git a/openspec/changes/add-ir-v3-support/.openspec.yaml b/openspec/changes/archive/2026-10-27-add-ir-v3-support/.openspec.yaml similarity index 100% rename from openspec/changes/add-ir-v3-support/.openspec.yaml rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/.openspec.yaml diff --git a/openspec/changes/add-ir-v3-support/design.html b/openspec/changes/archive/2026-10-27-add-ir-v3-support/design.html similarity index 98% rename from openspec/changes/add-ir-v3-support/design.html rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/design.html index fca8640a..40c27c8c 100644 --- a/openspec/changes/add-ir-v3-support/design.html +++ b/openspec/changes/archive/2026-10-27-add-ir-v3-support/design.html @@ -13,11 +13,11 @@ Code goes in
 (or json, toml, bash, diff). Give sections an id and
   list them in nav.toc.
 -->
-
-
-
-
-
+
+
+
+
+
 
 
 
diff --git a/openspec/changes/add-ir-v3-support/proposal.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/proposal.md similarity index 100% rename from openspec/changes/add-ir-v3-support/proposal.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/proposal.md diff --git a/openspec/changes/add-ir-v3-support/specs/ir/format-version/spec.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/format-version/spec.md similarity index 100% rename from openspec/changes/add-ir-v3-support/specs/ir/format-version/spec.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/format-version/spec.md diff --git a/openspec/changes/add-ir-v3-support/specs/ir/migrate/spec.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/migrate/spec.md similarity index 100% rename from openspec/changes/add-ir-v3-support/specs/ir/migrate/spec.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/migrate/spec.md diff --git a/openspec/changes/add-ir-v3-support/specs/ir/v3/spec.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/v3/spec.md similarity index 100% rename from openspec/changes/add-ir-v3-support/specs/ir/v3/spec.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/v3/spec.md diff --git a/openspec/changes/add-ir-v3-support/specs/ir/v4/spec.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/v4/spec.md similarity index 100% rename from openspec/changes/add-ir-v3-support/specs/ir/v4/spec.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/ir/v4/spec.md diff --git a/openspec/changes/add-ir-v3-support/specs/tools/ir-interchange/spec.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/tools/ir-interchange/spec.md similarity index 100% rename from openspec/changes/add-ir-v3-support/specs/tools/ir-interchange/spec.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/specs/tools/ir-interchange/spec.md diff --git a/openspec/changes/add-ir-v3-support/tasks.md b/openspec/changes/archive/2026-10-27-add-ir-v3-support/tasks.md similarity index 100% rename from openspec/changes/add-ir-v3-support/tasks.md rename to openspec/changes/archive/2026-10-27-add-ir-v3-support/tasks.md diff --git a/openspec/specs/ir/format-version/spec.md b/openspec/specs/ir/format-version/spec.md new file mode 100644 index 00000000..7510ec7c --- /dev/null +++ b/openspec/specs/ir/format-version/spec.md @@ -0,0 +1,57 @@ +# ir/format-version Specification + +## Purpose +Defines how morphir-python reads, normalizes, checks, and writes the `formatVersion` field of a versioned Morphir IR document, so every codec and the CLI reject unsupported input the same way, with stable diagnostic codes, before any distribution decoding starts. + +## Requirements + +### Requirement: Permissive format version recognition +The reader SHALL accept a JSON integer `N`, a string `"N"`, and a string `"N.minor.patch"`, and normalize each to an exact release (`3`, `"3"` and `"3.0.0"` all mean 3.0.0). Components MUST be unsigned 32-bit base-10 integers with no leading zeros. Booleans, null, floats, negatives, collections, whitespace, prerelease and build suffixes MUST be rejected. (bead: morphir-ukvn.8) + +#### Scenario: Integer and string spellings normalize alike +- **WHEN** documents declare `formatVersion` as `3`, `"3"`, and `"3.0.0"` +- **THEN** each normalizes to release 3.0.0 + +#### Scenario: Boolean is not an integer +- **WHEN** a document declares `formatVersion` as `true` +- **THEN** recognition fails with `invalid_format_version_type` + +#### Scenario: Malformed release string +- **WHEN** a document declares `formatVersion` as `"3.0.0-alpha"` or `" 3.0.0"` +- **THEN** recognition fails with `invalid_format_version_syntax` + +### Requirement: Divergence from morphir-elm and the finos contract is deliberate +Accepting string spellings SHALL be documented as a deliberate divergence from morphir-elm, whose decoder accepts only an integer, and accepting `"N"` as a divergence from the finos/morphir contract, which accepts only integer `N` or `"N.minor.patch"`. Both exist to read files written by other tools during migration. (bead: morphir-ukvn.8) + +#### Scenario: Divergence is visible to users +- **WHEN** a user reads the package documentation for format version handling +- **THEN** it states which accepted spellings morphir-elm and the finos/morphir contract would reject + +### Requirement: Support table is checked before decoding +The reader SHALL declare the support table `[3.0.0,3.1.0),[4.0.0,4.1.0)` and MUST finish recognition and the support check before invoking any distribution decoder or migration. A release outside the table SHALL fail with `unsupported_format_version_major` or `unsupported_format_version_minor`. (bead: morphir-ukvn.8) + +#### Scenario: IR 3.1.0 is refused for now +- **WHEN** a document declares `formatVersion` `"3.1.0"` +- **THEN** reading fails with `unsupported_format_version_minor` before the distribution is decoded + +#### Scenario: Historical versions are refused +- **WHEN** a document declares `formatVersion` `1` or `2` +- **THEN** reading fails with `unsupported_format_version_major` and a message that formats 1 and 2 are follow-up work + +### Requirement: Stable format version diagnostics +Format-version failures SHALL use the finos/morphir contract codes: `missing_format_version`, `duplicate_format_version`, `invalid_format_version_type`, `invalid_format_version_syntax`, `format_version_out_of_range`, `unsupported_format_version_major`, and `unsupported_format_version_minor`. (bead: morphir-ukvn.8) + +#### Scenario: Missing version +- **WHEN** the document root has no `formatVersion` member +- **THEN** reading fails with `missing_format_version` + +#### Scenario: Duplicate version member +- **WHEN** the document root contains `formatVersion` twice +- **THEN** reading fails with `duplicate_format_version` rather than keeping the last value + +### Requirement: Canonical format version output +Writers SHALL emit `formatVersion` first, as the integer `N` for a baseline release `N.0.0` and as the exact release string otherwise, regardless of the spelling that was read. (bead: morphir-ukvn.8) + +#### Scenario: Read string, write integer +- **WHEN** a document read with `formatVersion` `"3.0.0"` is written again +- **THEN** the output declares `formatVersion` as the integer `3` diff --git a/openspec/specs/ir/migrate/spec.md b/openspec/specs/ir/migrate/spec.md new file mode 100644 index 00000000..2e7f66e6 --- /dev/null +++ b/openspec/specs/ir/migrate/spec.md @@ -0,0 +1,69 @@ +# ir/migrate Specification + +## Purpose +Defines migrations between Morphir IR format 3.0.0 and IR v4, including how attributes, documentation, and package versions map, so programs move forward safely and reverse migration fails explicitly when v4 content cannot be represented in v3. + +## Requirements + +### Requirement: Forward migrate v3 to v4 +The library SHALL migrate a valid v3 Library to a v4 Library. Unit type attributes SHALL become empty v4 type attributes; each v3 value attribute type SHALL become the v4 `inferred_type` with no source and no extensions; `WholeNumberLiteral` SHALL become `IntegerLiteral`. The caller MAY supply a package release version; otherwise it is absent. (bead: morphir-ukvn.8) + +#### Scenario: Shared Library content migrates forward +- **WHEN** a v3 Library is migrated to v4 +- **THEN** every v4 definition matches its source, and every value and pattern carries the source type as `inferred_type` + +#### Scenario: Package version comes from the caller +- **WHEN** a v3 Library is migrated with package version `1.2.0` +- **THEN** the v4 package identity carries `1.2.0`, and without that argument the version is absent + +### Requirement: Reverse migrate v4 to v3 with known failure paths +The library SHALL migrate v4 to v3. When the input uses concepts v3 cannot represent, migration SHALL fail with structured errors and MUST NOT invent or drop IR content, except as allowed by the dropped-information requirement. (bead: morphir-ukvn.8) + +#### Scenario: Specs and Application do not downgrade +- **WHEN** a v4 Specs or Application distribution is migrated to v3 +- **THEN** migration fails with `unsupported_v4_downgrade` naming the distribution kind + +#### Scenario: V4-only nodes are reported +- **WHEN** a v4 Library contains a `Hole`, `Native`, or `External` value, or a `DocumentLiteral` +- **THEN** migration fails with `unsupported_v4_downgrade` naming the construct and its location + +#### Scenario: Missing types are reported +- **WHEN** a v4 value, pattern, or value-definition input has no `inferred_type` +- **THEN** migration fails with `missing_inferred_type` at that location + +#### Scenario: Semantic attributes are reported +- **WHEN** v4 type attributes carry a source, constraints, or extensions, or value attributes carry extensions +- **THEN** migration fails with `unsupported_attributes_downgrade` + +#### Scenario: Absent member documentation is reported +- **WHEN** a v4 type or value member has no documentation +- **THEN** migration fails with `unsupported_documentation_downgrade`, because v3 member documentation is a required string + +#### Scenario: Compatible Library downgrades +- **WHEN** a v4 Library uses only concepts v3 can represent +- **THEN** migration succeeds with a v3 Library + +### Requirement: Dropped information is limited and reported +Reverse migration SHALL drop the package release version, which v3 cannot hold, and report `package_version_dropped`. Value source locations SHALL fail with `unsupported_attributes_downgrade` unless the caller opts in to dropping them, which reports `source_location_dropped`. Nothing else MAY be dropped. (bead: morphir-ukvn.8) + +#### Scenario: Package version is dropped with a warning +- **WHEN** a compatible v4 Library with package version `1.2.0` is migrated to v3 +- **THEN** migration succeeds and reports `package_version_dropped` + +#### Scenario: Source locations need opt-in +- **WHEN** a v4 value carries a source location and the caller did not opt in to dropping it +- **THEN** migration fails with `unsupported_attributes_downgrade` + +### Requirement: Shared subset round trips +For any valid v3 Library, migrating to v4 and back to v3 SHALL succeed and SHALL yield a Library semantically equal to the original. (bead: morphir-ukvn.8) + +#### Scenario: Elm fixture survives v3 to v4 to v3 +- **WHEN** a golden morphir-elm format-3 fixture is migrated to v4 and back +- **THEN** the result is semantically equal to the decoded fixture and reports only `package_version_dropped` if a version was supplied + +### Requirement: Migration results are explicit +Migration APIs SHALL return a success value with any warnings, or a failure holding one or more diagnostics. Each diagnostic SHALL expose a stable code, the construct name, and a location path. Codes SHALL reuse upstream codes where they exist. (bead: morphir-ukvn.8) + +#### Scenario: Failure is machine-readable +- **WHEN** reverse migration fails +- **THEN** callers can read each diagnostic's code, construct, and location without parsing message text diff --git a/openspec/specs/ir/v3/spec.md b/openspec/specs/ir/v3/spec.md new file mode 100644 index 00000000..f8b20172 --- /dev/null +++ b/openspec/specs/ir/v3/spec.md @@ -0,0 +1,54 @@ +# ir/v3 Specification + +## Purpose +Provides a first-class Morphir IR format 3.0.0 model and JSON codecs in Python, with morphir-elm as the normative source, so tooling can load and produce the IR that morphir-elm emits today. + +## Requirements + +### Requirement: morphir-elm is normative for v3 content +The v3 model and JSON shapes SHALL follow morphir-elm `Morphir.IR.*` types and `*.Codec` modules for format 3.0.0. Where finos/morphir v3 schemas or docs disagree with morphir-elm on 3.0.0 content, morphir-elm MUST win. (bead: morphir-ukvn.8) + +#### Scenario: Elm output decodes +- **WHEN** a `morphir-ir.json` written by morphir-elm `morphir make` is decoded +- **THEN** decoding succeeds without a Python-specific JSON variant + +### Requirement: V3 model mirrors morphir-elm concepts +The library SHALL expose a v3 model whose concepts match morphir-elm: names as word lists, paths, qualified and fully qualified names, access control, documented values, literals, types, values, patterns, modules, packages, and the `Library` distribution. A v3 `Library` SHALL carry type attributes of unit and value attributes of a v3 type, as in elm. (bead: morphir-ukvn.8) + +#### Scenario: Library distribution is representable +- **WHEN** a caller constructs a v3 Library with a package name, dependency specifications, and a package definition whose values carry types +- **THEN** the model keeps those parts without any v4-only construct + +#### Scenario: Library has no package version +- **WHEN** a caller constructs a v3 Library +- **THEN** the model has no package release version, because format 3.0.0 has none + +### Requirement: V3 JSON matches morphir-elm format 3.0.0 +The library SHALL encode and decode versioned distribution JSON with the shapes of morphir-elm `Morphir.IR.FormatVersion.Codec` and the codecs it uses. Node codecs SHALL take attribute codecs as parameters, as elm does. Version recognition SHALL use the shared format-version reader. (bead: morphir-ukvn.8) + +#### Scenario: Round trip versioned morphir-ir.json +- **WHEN** a valid morphir-elm `morphir-ir.json` with `formatVersion` 3 is decoded and encoded again +- **THEN** the output declares `formatVersion` 3 and its distribution is semantically equal to the input + +#### Scenario: String version is accepted on read +- **WHEN** an otherwise valid format-3 document declares `formatVersion` `"3.0.0"` +- **THEN** it decodes, and encoding writes the integer `3` + +#### Scenario: Wrong family at the v3 entry point +- **WHEN** a document with `formatVersion` 4 is passed to the v3 decoder +- **THEN** decoding fails with `unsupported_format_version_major` naming the found version and that only 3.0.x is accepted + +#### Scenario: Unknown distribution kind +- **WHEN** a format-3 document has a distribution tag other than `Library` +- **THEN** decoding fails with a structured error naming the tag + +### Requirement: V3 is independently usable +Callers SHALL be able to construct, inspect, encode, and decode v3 IR without converting to v4, and v3 classes SHALL be distinct from v4 classes. (bead: morphir-ukvn.8) + +#### Scenario: Inspect without migration +- **WHEN** a caller loads a format-3 distribution +- **THEN** they can read package, module, type, and value structure through the v3 API alone + +#### Scenario: Versions do not mix +- **WHEN** a caller passes a v4 node where the v3 model expects a v3 node +- **THEN** static type checking reports an error diff --git a/openspec/specs/ir/v4/spec.md b/openspec/specs/ir/v4/spec.md new file mode 100644 index 00000000..f616ab0d --- /dev/null +++ b/openspec/specs/ir/v4/spec.md @@ -0,0 +1,53 @@ +# ir/v4 Specification + +## Purpose +Keeps Morphir IR v4 as a first-class Python model alongside v3, and adds a draft v4 JSON codec, while stating plainly that upstream IR v4 is not yet stable and that this surface will follow upstream changes. + +## Requirements + +### Requirement: V4 remains a first-class IR surface +The library SHALL continue to expose the current IR v4 model (names, paths, types, values, modules, packages, and the Library, Specs, and Application distributions) as a first-class API usable without converting to v3. (bead: morphir-ukvn.8) + +#### Scenario: V4 types stay available +- **WHEN** a caller imports the v4 IR surface +- **THEN** they can construct Library, Specs, and Application distributions and the existing v4 type and value structures + +### Requirement: V4 package identity is explicit +The library SHALL make IR v4 reachable at `morphir.ir.v4`. `morphir.ir` and its existing submodule paths SHALL keep working and SHALL re-export the same class objects, so identity and `isinstance` checks agree across paths. (bead: morphir-ukvn.8) + +#### Scenario: Versioned and compatibility imports agree +- **WHEN** a caller imports a v4 class from `morphir.ir.v4` and the same class from `morphir.ir` or an existing submodule such as `morphir.ir.name` +- **THEN** both names refer to the same class object + +### Requirement: Package release version is optional +The v4 package identity SHALL hold a package name and an optional release version. A missing version SHALL be valid, because v4 JSON has no package-version member and release versions belong to distribution bindings. (bead: morphir-ukvn.8) + +#### Scenario: Unversioned package +- **WHEN** a caller constructs a v4 Library without a package release version +- **THEN** construction succeeds and the version reads as absent + +### Requirement: Draft v4 JSON codec +The library SHALL encode and decode v4 versioned distributions as `formatVersion` first and a distribution wrapped by its kind (`Library`, `Specs`, `Application`) using finos/morphir v4 JSON profile spellings at a pinned commit. The codec MUST be documented as draft and MUST NOT write morphir-elm format-3 shapes. (bead: morphir-ukvn.8) + +#### Scenario: V4 round trip +- **WHEN** a v4 Library built only from constructs the codec supports is encoded and decoded +- **THEN** the result equals the original, except that the package release version is absent + +#### Scenario: V4 encode is not format 3 +- **WHEN** a v4 distribution is encoded +- **THEN** the output declares `formatVersion` 4 and uses no format-3 tagged-array distribution shape + +#### Scenario: Unsupported spelling is refused +- **WHEN** v4 input contains a node kind or member the codec does not recognize +- **THEN** decoding fails with a structured error that names the node and its location, and does not guess + +#### Scenario: Construct with no draft spelling +- **WHEN** a caller encodes a v4 value that has no spelling in the pinned v4 JSON profile, such as the Python `Native` or `External` value nodes +- **THEN** encoding fails with a structured error naming the construct rather than dropping it + +### Requirement: Draft status is explicit +Documentation for the v4 model and codec SHALL state that IR v4 is a draft upstream, name the pinned finos/morphir commit, and state that spellings may change in later minor releases of this package. (bead: morphir-ukvn.8) + +#### Scenario: Draft notice is present +- **WHEN** a user reads the v4 codec documentation +- **THEN** it identifies v4 as draft and names the pinned upstream commit diff --git a/openspec/specs/tools/ir-interchange/spec.md b/openspec/specs/tools/ir-interchange/spec.md new file mode 100644 index 00000000..9c97f64f --- /dev/null +++ b/openspec/specs/tools/ir-interchange/spec.md @@ -0,0 +1,50 @@ +# tools/ir-interchange Specification + +## Purpose +Gives morphir-tools users commands to inspect versioned `morphir-ir.json` files and convert them between format 3.0.0 and draft IR v4, using the same library readers, codecs, and migrations that Python callers use. + +## Requirements + +### Requirement: Inspect versioned morphir-ir.json +The `ir inspect` command SHALL read a file with the shared format-version reader, decode it with the matching v3 or v4 codec, and report the normalized format version, the distribution kind, and the package name. (bead: morphir-ukvn.8) + +#### Scenario: Report format version 3 +- **WHEN** the user inspects a morphir-elm `morphir-ir.json` with `formatVersion` 3 +- **THEN** the tool reports format 3.0.0, kind Library, and the package name, and exits 0 + +#### Scenario: Unversioned IR is rejected clearly +- **WHEN** the input JSON has no `formatVersion` member +- **THEN** the tool exits non-zero, reports `missing_format_version`, and says the IR should be regenerated + +#### Scenario: Draft v4 is labelled +- **WHEN** the user inspects a v4 file +- **THEN** the report states that v4 support is draft + +### Requirement: CLI uses library code paths +The tools MUST NOT parse IR JSON with ad-hoc CLI code; reading, decoding, encoding, and migration SHALL go through the library APIs. (bead: morphir-ukvn.8) + +#### Scenario: Same result as the library +- **WHEN** the user inspects or converts a file that the library rejects +- **THEN** the tool reports the same diagnostic codes the library returns + +### Requirement: Convert between v3 and v4 +The `ir convert` command SHALL convert format 3.0.0 to v4 and v4 to format 3.0.0 through the library migrations. It SHALL accept an optional package version for upgrades and an opt-in flag to drop source locations on downgrade. Success writes the target file; failure exits non-zero, prints each diagnostic, and leaves no output file. (bead: morphir-ukvn.8) + +#### Scenario: Upgrade v3 file to v4 +- **WHEN** the user converts a valid format-3 Library file to v4 +- **THEN** the tool writes a `formatVersion` 4 file and exits 0 + +#### Scenario: Downgrade failure is visible +- **WHEN** the user converts a v4 file that cannot be represented in format 3.0.0 +- **THEN** the tool exits non-zero, prints each diagnostic code and location, and does not create the output file + +#### Scenario: Downgrade warnings are printed +- **WHEN** a downgrade succeeds with dropped information +- **THEN** the tool prints each warning code and still exits 0 + +### Requirement: Scope excludes formats 1, 2, and 3.1 +For this change, the tools MUST reject formats 1, 2, and 3.1.x rather than load or convert them. This differs from morphir-elm, which still reads format 1. (bead: morphir-ukvn.8) + +#### Scenario: Unsupported format version is named +- **WHEN** the user supplies a file whose `formatVersion` is 1, 2, or `"3.1.0"` +- **THEN** the tool exits non-zero with the format-version diagnostic code and says only 3.0.x and draft 4.0.x are supported