feat(ir): revisit v4 name canonicalization and initialism encoding - #744
Conversation
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.
✅ Deploy Preview for angry-raman-7c44f6 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
…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.
…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.
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,nuland their siblings as device names with any extension, so a type namedauxproduced an uncreatableaux.type.json, and the defaultMAX_PATHof 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
HTMLParserwhere Rust rendersHtmlParser, 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
morphir-irknowledge bundle, covering the encoding, the dual implementation, and a proposal to model the codec in Morphir itselfwebsite/static/schemas/docs/spec/ir/fixtures/naming-conformance.json, generated by Bun and TypeScript tooling undertools/, covering both encodings so it does not change when the constant is flippedDefects fixed along the way
All predate this work and were confirmed against the committed files.
AccessControlledaccepted only{"Public": ...}or{"access","value"}, never the flattened{"access", "TypeAliasDefinition"}form the document tree specification documents and every published example usesValueSpecificationdeclared a property literally nameddescriptionwhose schema was a string, because a description foroutputwas indented one level too far. That made the document tree schema fail the metaschema outrightschema:validateexpanded*.yamlthrough 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 launcherVerification
cargo check --workspace --all-targetsclean; morphir-rust at the merged ref is 1021 passed, 0 failedtools:typecheckclean understrictandnoUncheckedIndexedAccessKnown gaps, tracked
examples:validatestill fails on the two v4 examples, unchanged from before this branch:OpaqueTypeSpecificationis{}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.