Skip to content

Commit 713673b

Browse files
authored
feat(ir): revisit v4 name canonicalization and initialism encoding (#744)
Replaces the parenthesized acronym encoding in Morphir IR v4 names with an explicit segment model, and applies it across the specification, the JSON schemas and a new shared conformance corpus. The parenthesized form was adopted on the grounds that it is "URL-safe, readable, unambiguous". The URL premise is false: RFC 3986 normalizes only the scheme and host to lowercase and compares every other component, path included, case-sensitively. Parentheses were not buying safety either, since many libraries percent-encode them anyway, so the canonical form circulated in two spellings. An initialism is now a property of a word rather than a run of single letters, written as an uppercase segment (value-in-USD), so each backend applies its own convention. Names project onto the document tree through a reversible, all-lowercase escape that handles the Windows reserved device names and the MAX_PATH limits the draft left unhandled. The path budget defaults to long paths and is always recorded in the manifest. Also fixes several defects that predate this work: AccessControlled rejected the flattened form every published example uses, ValueSpecification declared a property whose schema was a string and failed the metaschema, and schema:validate relied on shell glob expansion that the default Windows shell does not do. Rationale is recorded as Decision Records 0001-0003 in the knowledge base. The reference implementation landed as finos/morphir-rust#117.
1 parent b1843f8 commit 713673b

26 files changed

Lines changed: 3035 additions & 283 deletions

.beads/issues.jsonl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,8 @@
274274
{"_type":"issue","id":"morphir-go-1e2.8","title":"Update example morphir.toml with workspace members","description":"Update examples/morphir.toml to document workspace member configuration:\n- Add commented [workspace.members] example\n- Add example [project] section\n- Document member discovery behavior\n- Show example directory structure in comments","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T20:03:01Z","created_by":"damian","updated_at":"2026-01-02T20:40:46Z","closed_at":"2026-01-02T20:40:46Z","close_reason":"Implementation complete with LoadedWorkspace, Project types, and example documentation","dependency_count":0,"dependent_count":0,"comment_count":0}
275275
{"_type":"issue","id":"morphir-d5xs","title":"kb sync push: export target does not check symlinked subdirectories","description":"sync push writes to resolve(target, path) where target is a user-named export directory, normally the upstream checkout. A symlinked subdirectory there would be followed. Same class as the mirror containment bugs fixed in #108, but deliberately left alone: the export target is not the mirror, and real upstream checkouts legitimately contain symlinks, so tightening it risks refusing valid exports. Decision (Damian, 2026-08-28): not hardening this now. Revisit if push ever writes somewhere less trusted than a checkout the user named, or if we grow a case where an export target is attacker-influenced.","status":"open","priority":4,"issue_type":"task","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-28T19:52:26Z","created_by":"Damian Reeves","updated_at":"2026-08-28T19:52:26Z","dependency_count":0,"dependent_count":0,"comment_count":0}
276276
{"_type":"issue","id":"morphir-8mgj","title":"test-resolution-probe","status":"closed","priority":4,"issue_type":"task","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-28T15:26:43Z","created_by":"Damian Reeves","updated_at":"2026-08-28T15:26:56Z","closed_at":"2026-08-28T15:26:56Z","close_reason":"resolution probe, not real work","dependency_count":0,"dependent_count":0,"comment_count":0}
277+
{"_type":"issue","id":"morphir-j442","title":"v4 schema has diverged from the published v4 examples","description":"mise run examples:validate fails on both v4 examples (books-and-records-example.json, complete-example.json). Pre-existing: they fail identically against the committed schema. Two distinct causes found so far. (1) AccessControlled accepted only {Public:...} or {access,value}, not the flattened {access, TypeAliasDefinition} form that docs/spec/ir/schemas/v4/document-tree-files.md documents and every example uses. Fixed by adding a branch and switching oneOf to anyOf. (2) Still failing: OpaqueTypeSpecification is {} in the examples but the schema expects an array at /distribution/Library/dependencies/morphir-SDK/modules/basics/types/int/OpaqueTypeSpecification. Needs a decision on which side is canonical for each divergence, then align schema and examples. Not naming related.","status":"open","priority":2,"issue_type":"bug","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-30T10:04:38Z","created_by":"Damian Reeves","updated_at":"2026-08-30T10:04:38Z","dependency_count":0,"dependent_count":0,"comment_count":0}
278+
{"_type":"issue","id":"morphir-xihy","title":"Implement classic-to-v4 IR converter","description":"The morphir ir migrate step in morphir-rust crates/morphir-tests/tests/acceptance.rs is a stub returning an error unconditionally, and morphir-core's converter module is disabled pending update to non-generic V4 types. 11 scenarios in migration.feature are tagged @pending and skipped. Remove the tag with the implementation.","status":"open","priority":2,"issue_type":"feature","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-30T10:01:38Z","created_by":"Damian Reeves","updated_at":"2026-08-30T10:01:38Z","dependency_count":0,"dependent_count":0,"comment_count":0}
279+
{"_type":"issue","id":"morphir-6uvn","title":"Model the v4 naming codec in Morphir","description":"Express encode/decode/escape/unescape/render for v4 names as a Morphir model in finos/morphir, per kb decision 0003 (Proposed). SDK surface verified sufficient: Char.isUpper/isLower/isDigit/toUpper/toLower and String.toList/fromList/split/join/uncons/foldl/any/all. A host-language bootstrap remains necessary because the model compiles to IR serialized by the rules it defines. Unresolved: where the model lives, whether morphir-elm can emit v4 IR.","status":"open","priority":2,"issue_type":"feature","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-30T10:01:27Z","created_by":"Damian Reeves","updated_at":"2026-08-30T10:01:27Z","dependency_count":0,"dependent_count":0,"comment_count":0}
280+
{"_type":"issue","id":"morphir-32c0","title":"Support loading v1 and v2 IR distributions","description":"load_distribution in morphir-rust crates/morphir-common/src/loader.rs recognizes V4 and Classic v3 only. real_v1.json and real_v2.json parse as neither, so those rows moved to a @pending scenario outline in legacy_loading.feature. The feature states the intent: load V1, V2 and V3 without immediate migration. Fold the rows back into the main outline when supported.","status":"open","priority":3,"issue_type":"feature","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-30T10:01:39Z","created_by":"Damian Reeves","updated_at":"2026-08-30T10:01:39Z","dependency_count":0,"dependent_count":0,"comment_count":0}
281+
{"_type":"issue","id":"morphir-7ypn","title":"Add a hashing primitive (SHA-256) to the Morphir SDK","description":"The v4 name path-length truncation rule suffixes a stem with the first 8 hex digits of a SHA-256 digest. The Morphir SDK has no hashing primitive, so that step cannot be modelled and stays native in each host, and the conformance corpus marks truncation cases host-verified. A hash is useful well beyond naming, so the surface belongs to the SDK. Morphir.SDK.UUID is the nearest precedent. Blocks full coverage for kb decision 0003.","status":"open","priority":3,"issue_type":"feature","owner":"957246+DamianReeves@users.noreply.github.com","created_at":"2026-08-30T10:01:38Z","created_by":"Damian Reeves","updated_at":"2026-08-30T10:01:38Z","dependency_count":0,"dependent_count":0,"comment_count":0}

.config/mise/config.toml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,31 @@ run = "jsonschema lint website/static/schemas/*.yaml website/static/schemas/*.js
8686
# ===== Validation Tasks =====
8787
[tasks."schema:validate"]
8888
description = "Validate schemas against metaschema"
89-
run = "jsonschema metaschema website/static/schemas/*.yaml"
89+
# The glob is expanded by the script rather than by the shell: the default
90+
# Windows shell does not expand "*.yaml", and bash is not a portable fallback
91+
# because a Windows box may carry only the WSL launcher.
92+
run = "bun run tools/validate-schemas.ts"
93+
94+
# The generator imports only node: builtins, so bun runs it without an install.
95+
# Only `tools:typecheck` needs tools/node_modules.
96+
[tasks."fixtures:naming-corpus"]
97+
description = "Generate the IR v4 name-encoding conformance corpus"
98+
run = "bun run tools/gen-naming-corpus.ts"
99+
100+
[tasks."fixtures:naming-corpus-check"]
101+
description = "Verify the name-encoding conformance corpus and its vendored copy are up to date"
102+
run = "bun run tools/gen-naming-corpus.ts --check"
103+
104+
[tasks."tools:install"]
105+
description = "Install dependencies for the Bun specification tooling"
106+
run = "bun install"
107+
dir = "tools"
108+
109+
[tasks."tools:typecheck"]
110+
description = "Type check the Bun specification tooling"
111+
run = "bun run typecheck"
112+
dir = "tools"
113+
depends = ["tools:install"]
90114

91115
[tasks.check]
92116
description = "Run all checks"
@@ -95,6 +119,7 @@ depends = [
95119
"lint",
96120
"examples:validate",
97121
"fixtures:validate",
122+
"fixtures:naming-corpus-check",
98123
"schema:validate",
99124
]
100125

docs/design/draft/ir/naming.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ sidebar_position: 2
88

99
The naming module uses **newtype wrappers** for type safety, **smart constructors** for validation, and a **canonical string format** for serialization.
1010

11+
:::caution Superseded
12+
The parenthesis abbreviation format described below has been replaced. Its stated rationale, "URL-safe", rested on
13+
the claim that URLs are case-insensitive; RFC 3986 makes the path component case-sensitive.
14+
15+
An initialism is now an uppercase segment (`value-in-USD`), and names project onto the document tree through a
16+
defined escape. The current specification is [Naming](../../../spec/draft/names.md); the rationale and schema patches
17+
are in [IR v4 name canonicalization and initialism encoding](../../proposals/ir-v4-name-encoding.md). This page has
18+
not yet been rewritten.
19+
:::
20+
1121
## Design Decisions
1222

1323
| Decision | Choice | Rationale |

docs/design/proposals/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This section contains design proposals for new Morphir features and capabilities
1313
Active proposals:
1414

1515
- [CLI-managed Desktop acquisition and launch](./desktop-acquisition-and-launch.md)
16+
- [IR v4 name canonicalization and initialism encoding](./ir-v4-name-encoding.md)
1617

1718
## Contributing
1819

0 commit comments

Comments
 (0)