Skip to content

feat(metadata): resolve references once after the tree - #444

Merged
dmealing merged 23 commits into
mainfrom
fm/mo-loader-resolve-once-build
Oct 21, 2026
Merged

dmealing merged 23 commits into
mainfrom
fm/mo-loader-resolve-once-build

Conversation

@dmealing

@dmealing dmealing commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR carries two lines of 1.1.0 rc.6 work, merged into one branch and validated in one run:

  1. Resolve-once references (ADR-0059). References resolve once, after the whole tree has
    loaded, in every port.
  2. rc.6 generated-file cleanup (ADR-0060). Every port deletes a pristine generated file it
    no longer emits. Java and Kotlin get a committed hash manifest. Every generator entry gets a
    handEdits setting.

The rc.6 cleanup branch was merged in as one merge commit. Both branches had used the number
ADR-0059, so the JVM hash-manifest ADR is now ADR-0060, and every reference to it is updated.

Part 1: resolve-once references (ADR-0059)

References now resolve once, after the whole tree has loaded, in every port (ADR-0059). Every source
keeps reference strings exactly as written. One resolver per port binds them after the last
validation pass, records the bound target and the authored spelling, and rewrites each value to its
canonical spelling before freeze. A model now loads to the same thing from YAML and from JSON,
whatever order its files load in, and an exported model loads back to itself.

This is the build of the resolve-once audit's section 5 (C1 = A, C2 = A), plus the six extra defects.
It folds into 1.1.0 rc.6.

What changed

  • One grammar in every source. ::Name is root-absolute, ..::Name is parent-relative (an
    over-drop is ERR_BAD_ATTR_VALUE), and pkg::Name is exact. A bare name looks in its own file's
    package, then at the root. Canonical JSON accepts every form, and ERR_RELATIVE_REF_IN_CANONICAL
    is retired in all ports and in ERROR-CODES.json.
  • Referrer package. A reference resolves in the package of the top-level node that declares it,
    so an inherited reference keeps its meaning. @of after @via uses the package the walk reaches.
    @spine uses the report's @from package. extends uses the declaring node's own file package.
  • Per-file default package, recorded on every node, nested ones included.
  • Serializer. It writes canonical values plus a document package rule, so a multi-package export
    loads back to itself. meta fmt uses an authored-refs mode, so it does not rewrite adopters'
    references.
  • Consumers. Codegen and runtime in every port read the bound target (TypeScript refTarget)
    instead of matching by short name. findObject accepts canonical values. Docs, describe text and
    refusal messages still quote the authored spelling. Generated type names use the target's own
    name.
  • Ejected generators keep compiling. The renamed or retyped Java and Kotlin helpers that
    generators ejected before 1.1.0 call (KotlinGenUtil.resolveObjectByShortOrFqn, the String
    forms of RelationshipReferences.resolveRelationshipReference and referenceCandidatesFor)
    stay as deprecated delegates. They now do the exact canonical lookup, never a short-name match.
  • Two new gates in every port. The YAML twin gate checks that each fixture loads to the same
    model from JSON and from YAML. The round-trip gate checks that each expected.json serializes back
    to itself. Shared exclusion files hold zero divergences: the round trip excludes nothing, and the
    twin excludes only nine fixtures that are format-native by design.
  • YAML-twin defects fixed.
    • C# YAML overlays defer as they do in JSON.
    • Java accepts a non-string field default: and list-valued aliases / seeAlso.
    • These fixes were reused from the yaml-twin-harness lane.
  • Fixtures. Shared goldens were regenerated from the TypeScript oracle. Ten new conformance
    fixtures were added (ref-*, error-ref-parent-over-drop, extends-bare-declaring-file-package,
    error-extends-bare-cross-package, package-default-per-file, serialize-multi-package-document,
    and two nested-overlay fixtures). error-relative-ref-in-canonical is removed. The corpus now has
    394 fixtures.
  • Docs.
    • ADR-0059 is new.
    • ADR-0032 is marked partly superseded, ADR-0042 amended, and ADR-0006 rows updated.
    • Updated: the porting guide, the inheritance feature doc, the authoring skill, CONFORMANCE.md
      counts and mapping, and the CHANGELOG 1.1.0 entries.

Open points from the audit (section 7), resolved

  1. @implementedBy is excluded from the reference set. Its grammar names members as well as
    objects, and the requirement gate resolves it separately.
  2. Codegen and docs output.
    • Docs, describe text and refusal messages print the authored spelling.
    • Generated code names types by the target's name.
    • The neutral template data's targetRef is canonical (shared golden Order.txt:
      rel product -> shop::Product).
    • The docs-site origins table prints the canonical value (unchanged bytes).
  3. Round-trip exclusions: none. Derived nodes do not appear in expected.json.
  4. Spelling. The narrow rule is kept: a root-level target is ::Name from a packaged referrer
    and Name from a root-level one. The spelling uses the declaring node's package when the
    referrer package is empty.
  5. Packaged-referrer to root-target references: two values in one fixture's expected.json
    are spelled ::Name.
  6. Error-code registration. ERR_RELATIVE_REF_IN_CANONICAL lived only in ERROR-CODES.json
    and each port's enum or constants, not in expected-registry.json. It is removed everywhere.
  7. Java and Kotlin.
    • Eight private matchers now use the shared resolver.
    • core-spring and spring-boot-starter have no further matchers.
    • Kotlin goes through metadata-ktx and codegen-kotlin.
  8. C# PendingOverlays reach the loader on the YAML path. The twin gate is green with zero
    divergences.
  9. Exported JSON: covered by the CHANGELOG "diffs to expect" bullet.
  10. Wall-clock: see below.

Deviations and judgement calls

  • fmt safety check. It compares canonical output (meaning), while fmt writes authored output.
    The audit's T8 text compared authored output.
  • Serializer package rule. In single-package mode each node keeps its own package. When the
    root has none and every node names P itself, the root package stays absent. Documented in
    ADR-0059 Decision 8.
  • Java top-level extends lookup stays type-scoped, as before. TypeScript's is not. No fixture
    separates the two; noted for a future parity pass.
  • C# key fallback. ResolutionKey falls back to an ancestor's package only when the node has
    neither its own package nor a recorded file default.

Part 2: rc.6 generated-file cleanup (ADR-0060)

What changed

  • Orphan sweep in every port. After a full run, gen deletes a file it wrote earlier and
    no longer emits when the file still matches its recorded hash. It refuses a hand-edited one
    by name. verify --codegen uses the same rule, so its remedy for an extra: file is correct.
    Before, Python never deleted such a file and verify told the user to regenerate, which could
    not remove it.
  • Java and Kotlin hash manifest. GeneratedFileWriter records each file in the committed
    .metaobjects/.gen-state/.hashes.json, like TypeScript, Python and C#. An edited file is now
    refused instead of overwritten. The first run on an 8.0.x project adopts every generated file
    and deletes nothing. Migration notes: docs/features/migrations/jvm-codegen-hash-manifest.md.
  • handEdits: keep | overwrite on every generator entry, in every port. keep is the
    default.
  • New build-time check (JVM). Two executions that configure the same generator into the
    same <outputDir> fail generate and verify before anything is written, naming both.

Decisions already taken on this work

  • handEdits is captain-requested. The captain's words: "The overwrite of a file should be
    configurable. Someone may have accidentally edited the codegen file, but we actually still want
    it to be overridden. Can we do it per codegen config? Default would be not to overwrite." A
    review finding that the setting exceeds the intent was declined for that reason.
  • JVM uses option A: a committed hash manifest plus the same orphan sweep, so all five ports
    share one orphan rule.
  • Manifest owner = (generator registry id, resolved outputDir). The owner is not the Maven
    execution id, so default-cli, a renamed execution and the lifecycle execution share owners.
    A sweep deletes only its own pairs' orphans and the orphans of pairs the module no longer
    configures.
  • Configured owners come from Maven's model builder. They are the union of the base model,
    each profile of the module and its parent chain activated alone, and the active profile set.
    A profile's files are never treated as stranded by a build without that profile.
  • One owner per output folder. The same (generator, outputDir) pair in two executions is
    refused at build time with an error naming both executions (and their profiles). The earlier
    shared-pair sweep machinery is removed.
  • Phase-none executions are left out of the duplicate check but still count as configured
    owners, so on-demand output is never swept by a lifecycle build. Invoking a disabled execution
    explicitly when an enabled one owns the same pair is refused with the same error.
  • One file emitted by two generators with different content fails the goal.

Tests

On the merged head:

  • scripts/ci-local.sh --quick, then the full scripts/ci-local.sh. The full run found two red
    lanes, both fixed: the showcase site payload's fixture count, and the Kotlin ejected-generator
    compatibility test (see "Ejected generators keep compiling"). After the fixes,
    scripts/ci-local.sh --only java --only gates is green: Java and Kotlin conformance, the Java
    reactor, the Java and Kotlin integration tests, and every repository gate. The TypeScript,
    Python, C#, integration and Cube lanes were green in the full run, and nothing they cover
    changed after it.
  • Before the merge, each line of work had passed its own full local suites.

Validation runs

  • Resolve-once line: 2 no-mistakes runs. Run 1 (01M55DAEDRG1RP9H7164C885T1) was cancelled
    at review after three fix rounds, when the work paused; this combined run is run 2.
  • rc.6 cleanup line: 3 no-mistakes runs. Run 1 (01M54ZH6AVE8G8ND0QD32HDPBX) failed; run 2
    (01M559GKRX3SJC3HNGB4CWW61P) was aborted during its test step; this combined run is run 3.
  • This combined run: 01M5E2PWAGJRTTWX9ZJ475V143.

What Changed

  • Reference resolution moves out of the YAML desugar and into one resolver per port (resolve-references.ts, resolve_references.py, ResolveReferences.cs, References.java), which runs after validation and stores each resolved target. Every reference form (::Name, ..::Name, pkg::Name, bare) now resolves the same way in YAML and JSON, and canonical JSON accepts all of them. The serializer writes canonical spellings with per-object packages, and meta fmt keeps authored references as written. ADR-0059 records the decision and ERR_RELATIVE_REF_IN_CANONICAL is retired.
  • Orphan sweep in every codegen port: Python, C#, Java and Kotlin now delete files they wrote earlier and no longer generate, refusing hand-edited ones, as TypeScript already did. Java and Kotlin use a committed .hashes.json manifest (ADR-0060), and two runs of one generator into the same output folder are refused at build time.
  • Adds conformance gates: every fixture is loaded as JSON and as a YAML twin and must produce identical trees, and export followed by reload must round-trip. New fixtures cover bare extends across packages, inherited bare references, and per-file default packages. CHANGELOG 1.1.0 entries and migration notes are updated.
Validation

Risk Assessment

⚠️ Medium: The change redesigns reference binding in all five ports and adds codegen file deletion, both broad changes with real blast radius. In the fix-round and merge code I traced, the canonical lookups, bound-target reads, deprecated JVM compatibility delegates, fmt authored-ref mode and orphan sweeps behave as ADR-0059/0060 and the recorded decisions require, and I found no reachable defect beyond a stale changelog line, which I fixed inline. I did not read every changed file.

Testing

Drove the worktree TS meta CLI and Python metaobjects CLI live on disposable projects under /tmp. Resolve-once checks: JSON and YAML twin of the same model export identically, ::Apple binds the root object, bare Apple binds acme::Apple, a file with no package does not inherit a package, export round-trips, fmt keeps authored spellings, and an unresolved ref fails by name. Orphan sweep: TS and Python both delete an untouched file after a rename and keep a hand-edited one with a named refusal; Python handEdits: overwrite replaces the edited file while default refuses. Targeted and per-package suites pass (metadata, cli, codegen-ts, migrate-ts, runtime-ts, python orphan and overwrite tests). The single full server/typescript bun test run did not finish within the 1500s limit, so a full-suite green is unconfirmed. C#, Java and Kotlin ports were not driven live. Overall: every live scenario passed.

  • Live validation: ✅ go - 11 of 14 scenarios driven live against the product
Scenario Result Live Evidence
JSON: ::Apple binds root and bare Apple binds own package (acme::Apple), canonical export ✅ pass live s1-json-export.json; meta export -C /tmp/mo-rr shows @objectref "::Apple" and "acme::Apple"
YAML twin of same model exports identically to JSON (YAML ::Apple no longer binds acme) ✅ pass live s2-yaml-export.json; diff vs JSON export is empty
Export then reload then export is stable (canonical JSON round-trip) ✅ pass live meta export -C /tmp/mo-rt output equals /tmp/mo-rr export
meta fmt keeps authored refs as written (C2=A) ✅ pass live meta fmt on /tmp/mo-fmt keeps "::Apple" and bare "Apple" in meta.acme.json
Adversarial: unresolved @objectref ::Missing fails with a named error ✅ pass live s5-unresolved-ref.txt: exit 1, field.object "Basket.f" @objectref "::Missing" does not resolve
A file with no package does not inherit the first-loaded file's package; bare ref binds root ✅ pass live meta export -C /tmp/mo-pkg: Lone has no package; its @objectref stays bare
TS gen renames an entity: old file swept, verify --codegen green ✅ pass live meta gen after rename removes src/generated/Telemetry.ts; verify --codegen exit 0
Adversarial TS: hand-edited orphan is refused by name and kept ✅ pass live s8-ts-orphan-refused.txt
Python gen renames an entity: orphan swept, verify --codegen in sync ✅ pass live python3 -m metaobjects.cli gen then verify --codegen: 'in sync (2 file(s))'
Adversarial Python: hand-edited orphan refused by name and kept; default does not overwrite edited target ✅ pass live gen output: 'Refused to overwrite TelemetryReading.py'; exit 1
Overwrite policy configurable per generator (handEdits: overwrite) in Python ✅ pass live generators: [{name: entity, handEdits: overwrite}] overwrites edited file with warning naming it
C# orphan sweep and resolve-once parity live ⏸️ untested no Not driven. Would need dotnet SDK and a C# codegen project; not on PATH, and no workspace-local route was built. Covered only by unit tests in the diff.
Java and Kotlin orphan sweep, hash manifest, and same-folder build check live ⏸️ untested no Not driven. Needs Maven or Gradle and a JVM build of codegen-spring or codegen-kotlin; not run here. Covered only by unit tests in the diff.
Python YAML @of-after-@via and TS/YAML parity for @spine and extends in the other ports ⏸️ untested no Only the TS loader and Python gen were driven live. Cross-port YAML twin gates were not driven outside the TS CLI.
Evidence: TS and YAML export parity transcript (JSON export)
{
  "metadata.root": {
    "children": [
      {
        "object.value": {
          "name": "Apple",
          "package": "acme",
          "children": [
            {
              "field.string": {
                "name": "localOnly"
              }
            }
          ]
        }
      },
      {
        "object.value": {
          "name": "Basket",
          "package": "acme",
          "children": [
            {
              "field.object": {
                "name": "rootFruit",
                "@objectRef": "::Apple"
              }
            },
            {
              "field.object": {
                "name": "localFruit",
                "@objectRef": "acme::Apple"
              }
            }
          ]
        }
      },
      {
        "object.value": {
          "name": "Apple",
          "children": [
            {
              "field.string": {
                "name": "rootOnly"
              }
            }
          ]
        }
      }
    ]
  }
}
Evidence: YAML twin export (identical to JSON)
{
  "metadata.root": {
    "children": [
      {
        "object.value": {
          "name": "Apple",
          "package": "acme",
          "children": [
            {
              "field.string": {
                "name": "localOnly"
              }
            }
          ]
        }
      },
      {
        "object.value": {
          "name": "Basket",
          "package": "acme",
          "children": [
            {
              "field.object": {
                "name": "rootFruit",
                "@objectRef": "::Apple"
              }
            },
            {
              "field.object": {
                "name": "localFruit",
                "@objectRef": "acme::Apple"
              }
            }
          ]
        }
      },
      {
        "object.value": {
          "name": "Apple",
          "children": [
            {
              "field.string": {
                "name": "rootOnly"
              }
            }
          ]
        }
      }
    ]
  }
}
Evidence: Unresolved ref error
meta: field.object "Basket.f" @objectRef "::Missing" does not resolve to an object.
Evidence: TS orphan refused for hand edit
meta: meta gen: 1 generated file(s) are no longer generated but have been edited by hand, so they were NOT deleted: src/generated/TelemetryReading.ts. Delete each one, or, if what produced it was renamed, move your edits into the newly generated file first and then delete it (regeneration cannot follow a rename).
gen[1]{file,status}:
  src/generated/Reading.ts,new
summary: 1 written
help[2]: typecheck the generated code with `npx tsc`,create your database tables with `meta migrate --from-db --db <url> --dialect sqlite --slug init --apply`
antiPatterns:
  status: ran
  total: 0
  rows: []
warnings[1]: "meta gen: 1 generated file(s) are no longer generated but have been edited by hand, so they were NOT deleted: src/generated/TelemetryReading.ts. Delete each one, or, if what produced it was renamed, move your edits into the newly generated file first and then delete it (regeneration cannot follow a rename)."
  • Outcome: ⚠️ 1 warning across 1 run (1h3m33s)

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ CHANGELOG.md:1433 - The 1.1.0 CHANGELOG said Java referenceCandidatesFor / resolveRelationshipReference lose their name overloads. Commit 2af9d5f restored those overloads as deprecated delegates, and also kept Kotlin KotlinGenUtil.resolveObjectByShortOrFqn as a deprecated delegate, so the claim was wrong. The changelog also never said that resolveObjectByShortOrFqn stopped matching short names. I reworded the bullet: C# loses its string overloads; Java and Kotlin keep theirs, deprecated, for generators ejected before 1.1.0; those overloads now do an exact canonical lookup, so a short name no longer matches a packaged object; callers should pass the target object or use lookupCanonicalObject.
⚠️ **Test** - 1 warning
  • ⚠️ server/typescript - Full server/typescript bun test did not finish: killed at the 1500s background limit with no output (bun buffers until exit). Per-package runs of metadata, cli, codegen-ts, migrate-ts and runtime-ts all pass, but one full-suite run is not confirmed green.
  • Live validation: ✅ go - 11 of 14 scenarios driven live against the product
Scenario Result Live Evidence
JSON: ::Apple binds root and bare Apple binds own package (acme::Apple), canonical export ✅ pass live s1-json-export.json; meta export -C /tmp/mo-rr shows @objectref "::Apple" and "acme::Apple"
YAML twin of same model exports identically to JSON (YAML ::Apple no longer binds acme) ✅ pass live s2-yaml-export.json; diff vs JSON export is empty
Export then reload then export is stable (canonical JSON round-trip) ✅ pass live meta export -C /tmp/mo-rt output equals /tmp/mo-rr export
meta fmt keeps authored refs as written (C2=A) ✅ pass live meta fmt on /tmp/mo-fmt keeps "::Apple" and bare "Apple" in meta.acme.json
Adversarial: unresolved @objectref ::Missing fails with a named error ✅ pass live s5-unresolved-ref.txt: exit 1, field.object "Basket.f" @objectref "::Missing" does not resolve
A file with no package does not inherit the first-loaded file's package; bare ref binds root ✅ pass live meta export -C /tmp/mo-pkg: Lone has no package; its @objectref stays bare
TS gen renames an entity: old file swept, verify --codegen green ✅ pass live meta gen after rename removes src/generated/Telemetry.ts; verify --codegen exit 0
Adversarial TS: hand-edited orphan is refused by name and kept ✅ pass live s8-ts-orphan-refused.txt
Python gen renames an entity: orphan swept, verify --codegen in sync ✅ pass live python3 -m metaobjects.cli gen then verify --codegen: 'in sync (2 file(s))'
Adversarial Python: hand-edited orphan refused by name and kept; default does not overwrite edited target ✅ pass live gen output: 'Refused to overwrite TelemetryReading.py'; exit 1
Overwrite policy configurable per generator (handEdits: overwrite) in Python ✅ pass live generators: [{name: entity, handEdits: overwrite}] overwrites edited file with warning naming it
C# orphan sweep and resolve-once parity live ⏸️ untested no Not driven. Would need dotnet SDK and a C# codegen project; not on PATH, and no workspace-local route was built. Covered only by unit tests in the diff.
Java and Kotlin orphan sweep, hash manifest, and same-folder build check live ⏸️ untested no Not driven. Needs Maven or Gradle and a JVM build of codegen-spring or codegen-kotlin; not run here. Covered only by unit tests in the diff.
Python YAML @of-after-@via and TS/YAML parity for @spine and extends in the other ports ⏸️ untested no Only the TS loader and Python gen were driven live. Cross-port YAML twin gates were not driven outside the TS CLI.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • meta export on JSON project and YAML twin project, diffed (identical)
  • meta export -> reload -> export round-trip (identical)
  • meta fmt on authored refs (::Apple and Apple kept)
  • meta export with unresolved ::Missing (exit 1, named error)
  • meta export no-package file does not inherit first-loaded package
  • meta init + eject entity + gen rename Telemetry->TelemetryReading + verify --codegen (TS orphan swept, verify green)
  • TS gen with hand-edited orphan (refused by name, file kept)
  • python3 -m metaobjects.cli gen rename sweep + verify --codegen (orphan removed, verify green)
  • python gen hand-edited orphan refused by name
  • python per-generator handEdits: overwrite overwrites hand-edited file
  • bun test packages/metadata (3694 pass)
  • bun test packages/cli (1717 pass)
  • bun test packages/codegen-ts (2927 pass)
  • bun test packages/migrate-ts (1062 pass)
  • bun test packages/runtime-ts (746 pass)
  • bun test test/codegen-drift-orphan-policy.test.ts (4 pass)
  • pytest tests/codegen/test_orphan_sweep.py tests/codegen/test_overwrite_policy_hash.py (19 pass)
  • full bun test in server/typescript: killed at 1500s, not completed
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…erator handEdits

- gen deletes a recorded, untouched file no generator emits any more (TS, Python, C#, Java, Kotlin)
- Java/Kotlin decide overwrites from the committed hash manifest (ADR-0059)
- handEdits: keep|overwrite per generator in all five ports
…t (ADR-0059)

A reference was interpreted in two places: the YAML desugar rewrote
reference values while lowering, and canonical JSON took them as written,
refused `::` and `..::`, and resolved them later in each port's loader.
The two paths disagreed, so the same model loaded differently from YAML
and from JSON, and depended on file order.

Now every source keeps references exactly as written. One resolver per
port binds them after the whole tree has loaded and validated, records
the bound target and the authored spelling, and rewrites each value to
its canonical spelling before freeze.

- One grammar in every source: `::Name` (root-absolute), `..::Name`
  (parent-relative, over-drop is ERR_BAD_ATTR_VALUE), `pkg::Name`, and a
  bare name (own file's package, then root). ERR_RELATIVE_REF_IN_CANONICAL
  is retired.
- The referrer package is the package of the top-level node that declares
  the attribute, so an inherited reference means what it meant where it
  was written. `@of` after `@via` uses the package the walk reaches, and
  `@spine` uses the report's `@from` package.
- The default package is per file, nested nodes included.
- The serializer writes canonical values plus a package rule that lets a
  multi-package export load back to itself. `meta fmt` keeps authored
  spellings.
- Consumers in codegen and runtime read the bound target (TS
  `refTarget`), never a short-name match. `findObject` accepts canonical
  values. Human-facing text keeps quoting the authored spelling.
- Two new gates in every port: the YAML twin (each fixture loads to the
  same model from JSON and from YAML) and the round trip (each
  expected.json serializes back to itself), with zero exclusions.
- YAML-twin defects fixed: C# YAML overlays now defer, and Java accepts a
  non-string field `default:` and list-valued `aliases`/`seeAlso`.
- Shared goldens regenerated from the TypeScript oracle. Ten new
  conformance fixtures; one retired.
…uild

Combines the rc.6 stale-file work (orphan sweep in every port, the JVM
hash manifest with (generator, outputDir) owners and the one-run-per-pair
build-time check, the per-generator handEdits setting) with the
resolve-once reference redesign, for one validation run.

Both branches had numbered their new ADR 0059. The resolve-once ADR keeps
ADR-0059 and the JVM hash-manifest ADR becomes ADR-0060; the CHANGELOG,
docs and test comments that cite it move with it. CHANGELOG conflicts
were additive and keep both sides.
…ors compile

A review round renamed KotlinGenUtil.resolveObjectByShortOrFqn and changed
RelationshipReferences' target parameter from a String to the bound node.
Generators an adopter ejected before 1.1.0 call the old forms, so
EjectedAtBaseGeneratorsCompatTest no longer compiled them. Restore the old
signatures as deprecated delegates that do the exact canonical lookup
(ADR-0059 R7), never a short-name match.
…n three docs and one stale audit-axis reference; ADRs, CHANGELOG and JVM docs already matched the change.
@dmealing
dmealing merged commit 58e22b2 into main Oct 21, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-loader-resolve-once-build branch October 21, 2026 03:11
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