Skip to content

Commit a048c83

Browse files
dmealingclaude
andcommitted
docs(changelog): 0.24.0 records the adopter batch — #326/#327, #332/#333, #334
Five issues an adopter filed against 0.24.0-rc.3, all fixed in this release and none of them previously in the changelog. Two are regressions from the metadata-source-resolution work in this same section, which is why they belong in it rather than in a follow-up. The Python `libraries` entry's closing parenthetical is corrected: it said the TypeScript CLI "still lacks the key — parity follow-up", and both remaining ports now have it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 593af0e commit a048c83

1 file changed

Lines changed: 148 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,7 @@ same refs (path under `library/` minus `.yaml`), same on-disk-first resolution:
851851
config is a `ConfigError` naming the valid ones, while the programmatic API keeps
852852
TypeScript's silent skip: a name typed into a config file is a mistake worth failing on,
853853
where an API caller asking for a package this version does not ship should still load
854-
its own metadata. (The TypeScript CLI still lacks the key — parity follow-up, not drift
855-
introduced here.)
854+
its own metadata. (The TypeScript CLI gained the same key in this release — see below.)
856855

857856
Three gates ship with it, since each of these failed silently before:
858857

@@ -880,6 +879,153 @@ base, so it appears in codegen output and in a schema diff unless filtered. Docu
880879
the Python prompts reference rather than changed, because `library/ai/llm-call.yaml` is
881880
shared by every port and splitting it is a cross-port decision.
882881

882+
### Added — `libraries` on the TypeScript CLI and the whole feature on Java ([#333](https://github.com/metaobjectsdev/metaobjects/issues/333), [#332](https://github.com/metaobjectsdev/metaobjects/issues/332))
883+
884+
**No new vocabulary — port parity, twice.** `expected-registry.json` is untouched.
885+
886+
The Python entry above closes with "the TypeScript CLI still lacks the key". Both remaining
887+
ports now have it, and the Java gap was the larger of the two.
888+
889+
**TypeScript (#333)**`libraries` existed only on `MetaDataLoader.fromDirectory`. No CLI
890+
command uses that factory; every one of them goes through `loadMemory` with a resolved file
891+
list. So the option reached nothing, while the generators that consume a library are
892+
registered *for the CLI* — the generator was reachable from the command line with its input
893+
unreachable through it, and an adopter following the documented
894+
`extends: "metaobjects::ai::LlmCallBase"` got `ERR_UNRESOLVED_SUPER` pointing at their own
895+
metadata. `metaobjects.config.ts` now takes `libraries?: readonly string[]`, beside
896+
`providers` because it answers the same shape of question — what does this project's model
897+
need in scope beyond the files it declares.
898+
899+
Threaded to `loadMemory` at all eight load sites (`gen`, `verify`, `docs`, `prompt-snapshot`,
900+
and `migrate`'s four) through one `loadMemoryOptionsFrom` helper, not a spread pair copied
901+
eight times. That is the actual lesson of the bug: `providers` reached every command and
902+
`libraries` reached none, so a fix leaving eight independent opportunities to thread one and
903+
forget the other has not fixed the class.
904+
905+
`librarySources` is reached through a new **`@metaobjectsdev/metadata/library`** subpath and
906+
imported lazily, never from the root barrel — it reads `node:fs`, and a root-reachable static
907+
import drags Node built-ins into every consumer's module graph, which is the
908+
[#287](https://github.com/metaobjectsdev/metaobjects/issues/287) bundle defect. The subpath
909+
exists for exactly the reason `./constants` does.
910+
911+
**Java (#332)** — the port shipped `LlmTraceHelperGenerator` and *no way at all* to load the
912+
metadata it consumes: no `libraries` option, no embed. What let that survive is the more
913+
useful half: every test of that generator declares its own `LlmCallBase` inline under a
914+
different package, so the suite could not tell a world where the library loads from one where
915+
it does not exist. That is the bypass ADR-0024 already names — "the green tests pass only
916+
because they bypass the shipped base with bespoke entities."
917+
918+
- `com.metaobjects.library.EmbeddedLibrary` — a generated **class** of string constants, not a
919+
`src/main/resources` copy. A resource can be dropped or mangled by build configuration
920+
(resource filtering, shading, repackaging) and the failure surfaces much later as
921+
`ERR_UNRESOLVED_SUPER` against the adopter's own metadata; a class constant cannot go
922+
missing without the class going missing. Same rationale Python records for its source module.
923+
Emitted by the **existing** `scripts/generate-embedded-library.ts` rather than a second
924+
script — two scripts walking one tree are two things that can drift, and an embed's whole job
925+
is to be byte-identical to its source.
926+
- `com.metaobjects.library.LibrarySources` — on-disk-first, embedded fallback, and an
927+
unrecognised package contributing no sources, matching every other port.
928+
- The opt-in: `MetaDataLoader.setLibraries(...)`, a `fromDirectory(..., libraries)` overload,
929+
`LoaderConfiguration.getLibraries()` (a `default` method — this interface is the build-tool
930+
seam and an implementor outside this repo must keep compiling), and a pom
931+
`<loader><libraries><library>ai</library></libraries>`.
932+
933+
An unknown name in a pom or a `metaobjects.config.ts` is a **hard error listing the packages
934+
this version ships**, while the programmatic door keeps the silent skip. Both ports draw the
935+
same line Python did: an API caller asking for a package this version does not ship should
936+
still load its own metadata, but a name a human typed into a config file is a mistake worth
937+
failing on — skipped, it resurfaces as `ERR_UNRESOLVED_SUPER` pointing at the wrong file.
938+
939+
Gated on Java by the freshness comparison, the positive arm, and a negative arm asserting the
940+
same model still fails **and that the failure names `LlmCallBase`** — the loader wraps the real
941+
diagnostic in a "Failed to load from directory <path>" envelope that names nothing, and the
942+
first draft of that assertion passed on the envelope. Plus `TraceHelperOnShippedLibraryTest`,
943+
which runs the generator against the **shipped** base with ADR-0024 FIX #1 asserted both
944+
directions. The freshness gate was proven by breaking it, not by its silence.
945+
946+
C# has the same gap and is not addressed here.
947+
948+
### Fixed — two files, two questions: a sub-project's config governs its own codegen ([#326](https://github.com/metaobjectsdev/metaobjects/issues/326), [#327](https://github.com/metaobjectsdev/metaobjects/issues/327))
949+
950+
Both are regressions from the metadata-source-resolution work in this same release, both
951+
reported by an adopter evaluating `0.24.0-rc.3`, and both come from one conflation: treating a
952+
single directory as the answer to two different questions. Design §4.6 already draws the line —
953+
`.metaobjects/config.json` says where metadata comes from (port-neutral, read by all five CLIs,
954+
reasonably repo-global in a polyglot monorepo); `metaobjects.config.ts` says how **this**
955+
TypeScript package generates code.
956+
957+
**#326`meta gen` could not run at all in a Maven- or pip-rooted monorepo.** Setting
958+
`projectRoot = collection.configDir` closed a real divergence (a subdirectory run silently
959+
defaulting `columnNamingStrategy` and emitting a migration that renamed every column), but it
960+
assumed the two files are always co-located. When they are not — repo root declares the
961+
collection, the JS app underneath carries the TS config — `gen` demanded `metaobjects.config.ts`
962+
at the ancestor and exited 2 against a directory that has one sitting in it. `0.23.1` wrote 376
963+
files on the same tree. The obvious workaround did not reach CI either, since `.metaobjects/` is
964+
gitignored in such sub-projects, so the file existed only on developer machines.
965+
966+
`metaobjects.config.ts` now gets its **own** nearest-ancestor walk from the invocation
967+
directory, with collection discovery untouched. Nearest wins, so a subdirectory declaring
968+
nothing still walks up to the project root's config exactly as before; the fallback is the
969+
collection's directory, so this can only ever move the answer *closer* to the invocation, and
970+
a project with no TS config anywhere keeps today's diagnostics unchanged. In every `meta init`
971+
project the two walks return the same directory by construction.
972+
973+
Applied at all five sites, not only the one that failed loudly. `gen` was the sole hard
974+
failure; `verify --codegen` reported "no config" for a package that has one, `docs` silently
975+
dropped its providers and skipped the api surface, `prompt-snapshot` lost its providers, and
976+
`migrate` defaulted `columnNamingStrategy` — the identical rename-every-column failure the
977+
co-location existed to prevent, reached from the other side. Everything each config **names**
978+
follows it: `outDir`/`targets` and the `.metaobjects/.gen-state/` merge base that mirrors that
979+
output (per-package, or two apps sharing one collection clobber each other's), `docs.outDir`,
980+
the adopter `templates/` chain, the owned `codegen/docs-site/` theme, and `verify.testFiles`.
981+
`.metaobjects/` state — migrations, snapshots, the operational block, `wrangler.toml`
982+
discovery — stays on the collection's directory.
983+
984+
**#327`meta docs <path>` stopped scoping.** The positional has always meant "document this",
985+
and before sources were resolvable it read `<path>/metaobjects/` and nothing else. Routing docs
986+
through `resolveCollection(<path>)` turned the argument into a *starting point for an upward
987+
walk*, so the nearest ancestor config was found and its declared sources were unioned in.
988+
Nothing was lost — a set-diff of an adopter's page lists shows zero pages dropped — but pages
989+
from unrelated trees appeared inside a tree meant to hold prompt contracts only. Unlike `gen`
990+
this fails **open**: exit 0, just more pages than anyone asked for, invisible until someone
991+
counts them. An explicit positional now pins the collection (`resolveCollection`'s existing
992+
`explicitDir`); a bare `meta docs` still discovers. Both help blocks say so, since the old text
993+
described the argument as a root without saying what it does to the source set.
994+
995+
### Fixed — the private-host check asks where packages come from, not what strings a build file holds ([#334](https://github.com/metaobjectsdev/metaobjects/issues/334))
996+
997+
`tools/prerelease/detect-prerelease-pins.sh` flagged any RFC1918, loopback or link-local host
998+
anywhere in a manifest. An Android module with a LAN default for its own backend —
999+
`buildConfigField("String", "SERVER_URL", … "http://10.0.0.5:8000")` — failed it permanently.
1000+
That string is application config, where the built app looks for its own server; it says
1001+
nothing about where Gradle resolves dependencies. The project had never been linked to a
1002+
private registry and still exited 1 after a full, verified `unlink`.
1003+
1004+
The header calls this check load-bearing and says to treat a failure as a build break, never as
1005+
advice. A permanent false positive inverts that: the only route to a green build is to stop
1006+
running the check or to train everyone to ignore it — which is how the true positive it exists
1007+
to catch gets waved through. It also made the `unlink` round-trip unverifiable, since `unlink`
1008+
ends by running the detector.
1009+
1010+
The script already stated the right principle one level up — "Only DEPENDENCY DECLARATIONS are
1011+
scanned… a check that cries wolf is a check people learn to ignore" — and that reasoning does
1012+
not stop at the file boundary. The host scan now splits the manifest list in two: files that
1013+
are package-resolution config end to end (`.npmrc`, every lockfile, `pip.conf`, `NuGet.config`,
1014+
`settings.xml`, `requirements*.txt`, …) are scanned whole, exactly as before; files carrying
1015+
both package sources and project configuration are scanned only inside the region that declares
1016+
a source — a Gradle `repositories { }` / `pluginManagement { }` block, a pom's
1017+
`<repositories>`/`<pluginRepositories>`/`<distributionManagement>`, an msbuild
1018+
`<RestoreSources>`, a `package.json` dependency block or `registry` key, a
1019+
`[[tool.uv.index]]`/`[[tool.poetry.source]]` section, a `gradle.properties` key naming a repo.
1020+
1021+
Checks 1, 3, 4 and 5 are untouched, and narrowing costs no detection on the npm path: a real
1022+
link writes the registry line into `.npmrc` and the resolved URL into the lockfile, both still
1023+
scanned whole. `settings.gradle`/`settings.gradle.kts` join the manifest list while here —
1024+
`pluginManagement { repositories { } }` lives there, so a private registry declared in one was
1025+
invisible to *every* check. Proven both directions against a fixture carrying each format twice,
1026+
once as app config and once as a real declaration; the negative half caught a case-sensitivity
1027+
bug in the properties rule on its first run.
1028+
8831029
## [0.23.2] — npm `0.23.2` · PyPI `0.23.2` · NuGet `0.23.2` · Maven `7.23.2`
8841030

8851031
A coordinated **PATCH** across all four registries.

0 commit comments

Comments
 (0)