Skip to content

Java ships the trace-helper generator but no libraries loader option, so metaobjects::ai::LlmCallBase cannot be loaded at all #332

Closed

Description

@dmealing

What

The Java port has LlmTraceHelperGenerator but no way to load the metadata that generator exists to consume. There is no libraries loader option and no library/ embed on that port, so extends: metaobjects::ai::LlmCallBase cannot resolve — the documented adopter path fails with ERR_UNRESOLVED_SUPER.

A generator shipped without its input.

Evidence (against main @ 0994d6e41)

$ git ls-tree -r main --name-only | grep LlmTraceHelperGenerator.java
server/java/codegen-spring/src/main/java/com/metaobjects/generator/spring/LlmTraceHelperGenerator.java

$ git grep -l "libraries" main -- 'server/java/**/*.java' | grep -v test
(no output)

The only references to LlmCallBase in the Java tree are in a test that declares its own inline under a different package:

server/java/codegen-spring/src/test/java/com/metaobjects/generator/spring/SpringAppliesToTest.java:79:
  { "object.entity": { "name": "LlmCallBase", "abstract": true, ... } }
:87:  "extends": "acme::shop::LlmCallBase"

That is the bypass ADR-0024 already called out — "the green tests pass only because they bypass the shipped base with bespoke entities" — which is why a port can ship a generator it cannot feed and stay green.

Why it matters

This is the same defect that was just closed on the Python port (libraries=[...] + library/ + the embed generator). Until Java has the equivalent, a JVM adopter following the documented extends metaobjects::ai::LlmCallBase path cannot get past load.

Suggested shape

Mirror the Python/TypeScript design rather than inventing a third: canonical library/**/*.yaml at the repo root stays the source, each port embeds it (Python embeds as a generated source module so no build config can drop it), and the loader gains a libraries opt-in that prepends those sources.

Worth adding with it, since none of these existed on Python either:

  • a byte-comparison gate so a stale embed cannot ship,
  • an assertion that extends FAILS without the opt-in and resolves with it — the negative half is what proves the opt-in is doing the work,
  • ADR-0024 FIX fix(cli): scaffold outDir defaults to src/generated, not ./src/db #1 (the recorder's row keys equal LlmCallBase's effective fields, both directions),
  • an acceptance test that RUNS the generated helper against the SHIPPED base rather than a hand-built fixture.

Related

  • TypeScript CLI has a narrower version of this (loader has libraries, CLI does not) — filed separately.

Activity

  1. dmealing commented on Aug 21, 2026

    @dmealing
    MemberAuthor

    Fixed on main. Java now has the whole feature, mirroring the Python/TypeScript design rather than inventing a third:

    • com.metaobjects.library.EmbeddedLibrary — a generated class of string constants, not a src/main/resources copy. A resource can be dropped or mangled by build configuration (resource filtering, shading, repackaging) and the failure would surface much later as ERR_UNRESOLVED_SUPER against the adopter's own metadata; a class constant cannot go missing without the class going missing. Same rationale Python records for its source module. Emitted by the existing scripts/generate-embedded-library.ts rather than a second script — two scripts walking one tree are two things that can drift.
    • com.metaobjects.library.LibrarySources — on-disk-first, embedded fallback, unrecognised package contributes no sources.
    • The opt-in: MetaDataLoader.setLibraries(...), a fromDirectory(..., libraries) overload, LoaderConfiguration.getLibraries() (a default method, so a build-tool implementor outside this repo keeps compiling), and a pom <loader><libraries><library>ai</library></libraries>. An unknown name in a pom is a hard failure listing what this version ships.

    All four gates you asked for:

    • the byte-comparison against the canonical tree, proven by breaking it rather than by its silence — appending a line to library/ai/llm-call.yaml turns it red naming the regeneration command;
    • extends asserted to fail without the opt-in and resolve with it. The negative arm is worth a note: its first draft asserted only that errors were non-empty, and it passed on the loader's "Failed to load from directory <path>" envelope, which names nothing. It now asserts the failure names LlmCallBase;
    • ADR-0024 FIX fix(cli): scaffold outDir defaults to src/generated, not ./src/db #1, both directions. One direction alone is worthless — "every row key is a field" passes on a recorder that writes nothing, "every field is a row key" passes on one that writes the whole world;
    • TraceHelperOnShippedLibraryTest runs the generator against the shipped base, so the suite can now tell a world where the library loads from one where it does not exist. That was the gap that let this ship.

    C# has the same gap and is not addressed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions