Skip to content

feat(ir): revisit v4 name canonicalization and initialism encoding - #744

Merged
DamianReeves merged 9 commits into
mainfrom
t3code/revisit-ir-name-canonicalization
Aug 30, 2026
Merged

feat(ir): revisit v4 name canonicalization and initialism encoding#744
DamianReeves merged 9 commits into
mainfrom
t3code/revisit-ir-name-canonicalization

Conversation

@DamianReeves

Copy link
Copy Markdown
Member

Revisits how Morphir IR v4 encodes names, replacing the parenthesized acronym form with an explicit segment model, and applies it across the specification, the JSON schemas and a new shared conformance corpus.

The reference implementation landed as finos/morphir-rust#117; this branch points the submodule at it.

Why

The parenthesized encoding (value-in-(usd)) 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.

Correcting that removes the reason to avoid case. Parentheses were not buying safety either. They are legal in a URI path, but many libraries percent-encode them anyway, so the canonical form circulated in two spellings, which defeats the "unambiguous" claim. They also need quoting in PowerShell and break Markdown link syntax, and this repository publishes docs/ through Docusaurus.

The constraint that actually bites is the filesystem, and it was unhandled: Windows reserves con, aux, nul and their siblings as device names with any extension, so a type named aux produced an uncreatable aux.type.json, and the default MAX_PATH of 260 is reachable from nested modules plus long names plus .value.json.

What changes

An initialism is now a property of a word rather than a run of single letters, so each backend applies its own convention: Go renders HTMLParser where Rust renders HtmlParser, from the same IR. The canonical string writes it as an uppercase segment (value-in-USD).

Names project onto the document tree through a reversible escape (value-in-_usd.value.json) rather than verbatim. The result is entirely lowercase, so it is stable on case-insensitive filesystems, and it handles the reserved device names.

Both encodings are implemented. The case-free doubled-hyphen alternative (value-in--usd) is carried behind a compile-time constant while the decoder always accepts both; the two grammars are disjoint, so flipping the constant stays compatible with readers that already shipped.

Contents

  • Three Decision Records in a new morphir-ir knowledge bundle, covering the encoding, the dual implementation, and a proposal to model the codec in Morphir itself
  • The naming specification page rewritten around the segment grammar, with the escape and its worked examples
  • The four v4 schema files under website/static/schemas/
  • A shared conformance corpus at docs/spec/ir/fixtures/naming-conformance.json, generated by Bun and TypeScript tooling under tools/, covering both encodings so it does not change when the constant is flipped

Defects fixed along the way

All predate this work and were confirmed against the committed files.

  • AccessControlled accepted only {"Public": ...} or {"access","value"}, never the flattened {"access", "TypeAliasDefinition"} form the document tree specification documents and every published example uses
  • ValueSpecification declared a property literally named description whose schema was a string, because a description for output was indented one level too far. That made the document tree schema fail the metaschema outright
  • schema:validate expanded *.yaml through the shell, which the default Windows shell does not do. Rewritten as a Bun and TypeScript task, since bash is not a portable fallback on a machine carrying only the WSL launcher

Verification

  • 7 schemas valid against the metaschema; IR storage profile checks pass
  • Conformance corpus check passes, and its vendored copy in morphir-rust is byte-identical
  • cargo check --workspace --all-targets clean; morphir-rust at the merged ref is 1021 passed, 0 failed
  • tools:typecheck clean under strict and noUncheckedIndexedAccess

Known gaps, tracked

examples:validate still fails on the two v4 examples, unchanged from before this branch: OpaqueTypeSpecification is {} in the examples where the schema expects an array. That is systematic schema-versus-example drift needing a decision on which side is canonical, so it is filed rather than guessed at, along with the classic-to-v4 converter, v1 and v2 loading, the Morphir naming model, and the SDK hashing primitive it needs.

The parenthesized acronym encoding (value-in-(usd)) 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.

Correcting that removes the reason to avoid case. Parentheses were not
buying safety either: they are legal in a URI path but many libraries
percent-encode them anyway, so the canonical form circulated in two
spellings, and they need quoting in PowerShell and break Markdown links.

An initialism is now an uppercase segment (value-in-USD), and it is a
property of a word rather than a run of single letters, so each backend
applies its own convention. Names project onto the document tree through a
defined escape (value-in-_usd.value.json), which also handles the Windows
reserved device names and MAX_PATH limits that the draft left unhandled.

Records three decisions in a new morphir-ir knowledge bundle, applies the
change to the naming specification, and adds a shared conformance corpus
generated by Bun and TypeScript tooling under tools/, covering both the
shipped encoding and the case-free alternative.

The schemas under website/static/schemas/ still carry the retired
encoding; the proposal lists the exact patches.

Bumps the morphir-rust submodule to the matching implementation.
Replaces the parenthesized patterns in the v4 schemas with the segment
grammar: a segment is all-lowercase (a word) or all-uppercase (an
initialism), digits are allowed in both, and a mixed-case segment such as
"Usd" is rejected. Adds FileStem for the document tree projection, and
corrects the package examples, since morphir/sdk and morphir/SDK are now
different identities and the legacy [["morphir"],["s","d","k"]] decodes to
the latter.

Fixes two defects found while validating, both predating this change and
both confirmed against the committed schemas:

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
published example uses. Adds that branch, and switches oneOf to anyOf
because the flattened form is a superset of the legacy one, so
exactly-one matching cannot hold.

ValueSpecification declared a property literally named "description" whose
schema was a string, because an intended description for "output" was
indented one level too far. That made the document tree schema fail the
metaschema outright.

Rewrites schema:validate as a Bun and TypeScript task. It expanded
"*.yaml" through the shell, which the default Windows shell does not do,
and bash is not a portable fallback because a Windows box may carry only
the WSL launcher.

The remaining examples:validate failures are unrelated to naming and are
tracked separately.
Records five issues surfaced while applying the v4 name encoding: the
Morphir model of the naming codec and the SDK hashing primitive it needs,
the classic-to-v4 converter and v1/v2 loading behind the scenarios tagged
@pending in morphir-rust, and the divergence between the v4 schemas and
the published v4 examples.

Appends only those five to the tracked export rather than rewriting it.
The bootstrapped database also holds sixteen issues from upstream commits
this branch does not have, and a full export would pull them in.
Picks up the rustfmt fix on feat/v4-name-encoding so the parent branch
points at the head of the branch its PR depends on.
A legacy run of single-character digits collapsed into an initialism, but
the uppercase encoding writes an initialism by uppercasing it, which does
nothing to digits, so decoding classified it as a word and identity
changed silently. Only letter runs collapse now.

Adds the digit-run cases the corpus was missing and a generator
self-check that no corpus segment is a digits-only initialism, and
advances the morphir-rust ref to the matching fix.
finos/morphir-rust#117 landed as a squash, so the branch commits this ref
pointed at no longer exist on any branch. Re-points at the squashed commit
on main, whose content is identical to the branch head.
@netlify

netlify Bot commented Aug 30, 2026

Copy link
Copy Markdown

Deploy Preview for angry-raman-7c44f6 ready!

Name Link
🔨 Latest commit c397b6b
🔍 Latest deploy log https://app.netlify.com/projects/angry-raman-7c44f6/deploys/6a943052e011ce000866256d
😎 Deploy Preview https://deploy-preview-744--angry-raman-7c44f6.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T13:07:23.509232Z b7521ba PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7521ba93d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/static/schemas/morphir-ir-v4.yaml Outdated
Comment thread website/static/schemas/morphir-ir-v4.yaml Outdated
Comment thread tools/gen-naming-corpus.ts
…ding

Addresses four review findings on #744.

The document tree specification still instructed readers to emit
user-(id).type.json, forbade uppercase and underscores, and required the
filename to equal the name verbatim. Those rules contradicted the accepted
encoding, so anyone following the published page would produce IR the
updated schemas reject. The page now states that a filename is the escaped
stem rather than the name, and shows the reserved-device and initialism
cases.

Three schema defects, all self-inflicted:

The compact reference patterns used a loose [a-zA-Z0-9-/] class, so
morphir/Usd:basics#int validated even though FQNameString rejects a
mixed-case segment. They now reuse the real grammar.

FileStem disallowed internal underscores and therefore rejected every
truncated stem, a form the same specification mandates.

The legacy array item pattern required a leading letter, which rejected
the digit-leading words real legacy data carries. The schema's own
FR2052A example depends on 2052 being a legal legacy word.

Path length is no longer one constant. Windows 10 1607 and later can lift
MAX_PATH through LongPathsEnabled, and POSIX targets were never bound by
it, so the budget is detected per target and overridable. Because a tree
written under a raised budget is not portable and nothing in the tree
would say so, a distribution that used anything other than the portable
budget records pathBudget in its manifest.
fb90021 used `git add -A` and picked up three submodule working trees
that were checked out at older commits than the branch recorded. That
reverted morphir-elm, morphir-examples and morphir-moonbit, and CI failed
installing the Elm toolchain at the older morphir-elm ref.

Restores all three to the refs on main. Only morphir-rust is intentionally
advanced by this branch.
Flips the default from the most restrictive target to 4000. Long paths are
the ordinary case: PATH_MAX is 4096 on Linux and macOS, and Windows has
been able to lift MAX_PATH since version 1607. Defaulting to 200 made
every tree pay for the least capable consumer, and that payment is lossy,
because a truncated stem is not reversible and forces the module to carry
a fileNames map.

A deployment bound by MAX_PATH lowers the budget to 200 instead.

The restrictive case has not gone away: LongPathsEnabled is opt-in rather
than default, a Win32 process must also declare longPathAware in its
manifest, and Git for Windows ships core.longpaths=false, so a stock
Windows clone of a default tree can fail to check out.

That residual risk is why pathBudget is now written unconditionally rather
than only when it differs from the default. A reader that cannot satisfy
the recorded budget says so once, up front, and no consumer has to guess
what an unmarked tree assumed. A missing value reads as 4000 for trees
written before the field existed.

Adds pathBudget to the distribution manifest schema.
@DamianReeves
DamianReeves merged commit 713673b into main Aug 30, 2026
10 checks passed
DamianReeves added a commit that referenced this pull request Aug 30, 2026
…le/Reference decision (#747)

Follow-up to #744 (IR v4 name canonicalization).

- fix(schema): collapse the OpaqueTypeSpecification inner oneOf, whose second branch was a strict subset of the first, so the canonical empty form could never match exactly one branch.
- docs: move the Windows long-path guidance to the user-facing CLI page, covering both independent switches (git core.longpaths and the LongPathsEnabled registry value); AGENTS.md keeps only the tooling-author details.
- fix(spec): a bare JSON array in a type position is a Tuple; a parameterized reference carries the Reference wrapper. Resolves a contradiction between the document-tree spec and the schema in the schema favor, since a tuple of plain types and a one-argument reference share the same shape.
- fix(cli): embed a longPathAware application manifest in morphir.exe, without which the LongPathsEnabled registry switch does not apply to the process; and stop documenting a portable pathBudget the writer cannot yet produce (tracked as morphir-fur7).

mise run examples:validate now passes for the first time: 6 files, 0 errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant