You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(python): serve the shipped ai library, so the trace generator can load its input
The Python port shipped both halves of the AI trace stack and not the metadata in
between. `runtime/llm_recorder.py` was there. The `trace-helper` generator was there
and registered for the CLI. But there was no `library/` package and no `libraries`
loader option, so `metaobjects::ai::LlmCallBase` could not be loaded on this port at
all: the documented `extends: metaobjects::ai::LlmCallBase` failed with
ERR_UNRESOLVED_SUPER. A generator shipped without its input.
The more useful part is why nothing caught it. The codegen suite hand-builds its own
abstract `LlmCallBase` — exactly what ADR-0024 already recorded ("the green tests pass
only because they bypass the shipped base with bespoke entities"). A test that builds
its own fixture proves nothing about the path an adopter follows, so the port's docs
could describe a path that could not run.
No new vocabulary — no type, subtype, or attribute — so expected-registry.json is
untouched. This mirrors the TypeScript design rather than inventing a second one: same
package names, same refs (path under library/ minus .yaml), same on-disk-first order.
library/ library_sources(packages) returns a FileSource when the repo-root
library/ tree is reachable, so editing the canonical YAML takes
effect immediately, and falls back to the generated embed
otherwise (the wheel-in-site-packages case).
scripts/ regenerates the embed. Embedded as a .py module, not shipped as
package data, so no build-backend config can silently drop it.
libraries=[...] on from_directory (hence load_directory). Opt-in and lazily
imported: a load requesting no libraries neither pays the import
nor gets extra names in its model. Sources are prepended for a
deterministic, TS-matching order — NOT because resolution needs
it; resolve_supers runs once after every root merges.
config `libraries` threaded into the CLI's load path. The option first landed only
on the loader — which is also all TypeScript exposes — so
`metaobjects gen` still could not load the metadata the registered
trace-helper generator exists to consume. The generator was
reachable from the command line while its input was not. The TS
CLI still lacks the key; that is a parity follow-up, not drift
introduced here.
An unknown package name is a ConfigError naming the valid ones when it comes from a
config file, and a silent skip when it comes through the API — matching TypeScript.
A name a human typed is a mistake worth failing on; an API caller asking for a package
this version does not ship should still load its own metadata.
Four gates ship with it, because each of these failed silently before:
* the embed is byte-compared against the canonical YAML (the drift pattern already
used for spec/metamodel/), so a stale generated module cannot reach a wheel;
* `extends` is asserted to FAIL without the opt-in and resolve 18 inherited fields
with it — the negative half is what proves the opt-in is doing the work;
* ADR-0024 FIX#1 is now enforced: build_llm_call_row's keys equal LlmCallBase's
effective fields, both directions. The ADR asked for this gate; it did not exist;
* the acceptance test RUNS the generated helper against a capturing recorder and
asserts every key it writes is a field the entity declares. The first version of
that test asserted the strings voRequest/voResponse appeared in the emitted source,
against a fixture declaring neither column — so it passed while blessing a helper
that raises on its first write. That is the same bypass ADR-0024 warns about,
reappearing inside a test written to prevent it. A substring assertion over
generated code is not an end-to-end test.
Not addressed, and documented rather than changed: the opt-in also brings the library's
own concrete LlmCall entity (table llm_call) alongside the abstract base, so it appears
in codegen output and in a schema diff unless filtered. library/ai/llm-call.yaml is
shared by every port, so splitting it is a cross-port decision.
1639 tests pass; ruff clean on the changed files; no new mypy errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018k3CqBZLFkbP4Qs4FZhh96
0 commit comments