Repository navigation
feat: load project provider classes in every port - #529
Merged
Merged
Conversation
…by every command
A project that declares its own vocabulary (a new subtype, or attributes on a
core type) writes a provider class in each language whose tools load the
metadata and names it once in that port's config. Every metadata-loading
entry point now loads it.
- TypeScript: export, deps, fmt, types and upgrade now load the providers
in metaobjects.config.ts; verify and prompt-snapshot report a config that
fails to load instead of continuing without its providers.
- Python: fmt reads configured providers (and gains --config); verify's
--provider adds to the configured list instead of replacing it. The loader
keeps a file whose only parse errors are unknown subtypes, so its unknown
attributes are reported too.
- C#: new csharp.providers [{assembly, type}] list in .metaobjects/config.json,
loaded by gen, verify and fmt (ProviderLoader).
- Java/Kotlin: Maven plugin <providers> list for generate/verify/fmt/editor,
Spring MetaDataTypeProvider beans composed into the auto-configured loader,
LoaderOptions.setProviders, and a providers parameter on metadata-ktx.
ERR_UNKNOWN_ATTR is reported after ERR_UNKNOWN_SUBTYPE, as in the other ports.
- Conformance: provider-custom-vocabulary-success and
provider-custom-vocabulary-missing-provider-fails, run by every port.
- Skills: metaobjects-authoring teaches it with a worked reference per
language; metaobjects-codegen notes how a generator reads the attribute.
- Docs: extending-with-providers.md has a worked example per language and
drops the false SPI auto-discovery claim; port pages and recipe updated.
…our-codegen.md provider table (Java SPI claim, C#, TypeScript command list), server/java/README.md (ServiceLoader wording), and the recipe's per-language section now points to extending-with-providers.md instead of duplicating wiring facts. No code or tests touched; nothing unresolved.
Brings in #528 (read-only @SQL view column-collision exemption) and #530 (cross-field validators in generated create models). Conflicts resolved by keeping both sides: the conformance fixture count is 428 (main's 426 plus this branch's two provider-custom-vocabulary fixtures) in AGENTS.md, docs/CONFORMANCE.md and the site payload, and CHANGELOG keeps both entries. The metaobjects-authoring SKILL.md is held under its 500-line cap: the provider section's per-language wiring table moves into the per-language provider references it already pointed at, with a one-line pointer left in place, and the agent-context conformance fixtures are regenerated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
csharp.providerskey ({assembly, type}) in.metaobjects/config.json, and Java/Kotlin through the Maven plugin<providers>list (also-Dmeta.providers), SpringMetaDataTypeProviderbeans,LoaderOptions.setProviders, and a providers parameter on the metadata-ktx loaders. Metadata-loading commands in TypeScript (export, deps, fmt, types, upgrade, verify, prompt-snapshot) and Python (fmt, which gains--config; verify--providernow merges with the configured list) apply them. There is no ambient discovery: a class listed inMETA-INF/servicesis not loaded.provider-custom-vocabulary-successandprovider-custom-vocabulary-missing-provider-fails. The Python loader now keeps a file whose only parse errors are unknown subtype or type, so the unknown-attr pass still runs. The Java loader defersERR_UNKNOWN_ATTRuntil afterERR_UNKNOWN_SUBTYPE, matching TypeScript and C#. A load that failed still fails; only the error list order differs.metaobjects-authoringwith per-language provider references,metaobjects-codegen), regenerates their conformance fixtures, rewritesdocs/features/extending-with-providers.mdwith a worked example per language, and adds a CHANGELOG entry under Unreleased. Fixture count moves from 423 to 425.Validation
Risk Assessment
Testing
Drove two TS scenarios live through the built-from-source meta CLI in disposable projects: the forge-provider duplicate fix and a downstream-style custom vocabulary (pass with provider, refuse without). Drove the C# bad-assembly refusal live through dotnet meta verify. Python, Java/Kotlin and C# provider-loading checks ran only as unit, conformance or in-process tests, so they are recorded as untested for live behavior. Did not run the full repository regression suite (full ci-local, full Java reactor, full dotnet solution); only the baseline TS fast/unit lanes and the targeted suites above ran.
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
server/typescript/packages/cli/src/commands/export.ts:47-meta exportnow composes[...defaultLoadMemoryProviders, forgeTypesProvider, ...providers], whereprovidersis the config's list as written. The documented way to opt into forge vocabulary isproviders: [forgeTypesProvider](sdk memory.ts:133-134 and docs/features/extending-with-providers.md:483). A project that does this now has the forge provider twice, socomposeRegistrythrowsERR_PROVIDER_DUPLICATE_ID. The throw happens outside the try/catch at export.ts:47, someta exportfails for the projects that hold descriptive records. Before this change export ignored config providers and worked. Fix: drop config providers whoseidis already in the base set (or add forge only when the config does not list it), and add a test that uses a config withproviders: [forgeTypesProvider].server/csharp/MetaObjects/Config/ProviderLoader.cs:62-ProviderLoader.LoadAssemblycallsAssemblyName.GetAssemblyNameandAssemblyLoadContext.LoadFromAssemblyPathwithout handlingBadImageFormatExceptionorFileLoadException. Example:csharp.providers[0].assemblypoints at a native dll, a.pdb, or a dll built for an incompatible runtime. The exception then escapesMetadataLocation.Resolve.ResolveMetadataDirOrExit(Program.cs:323-337) andCodegenCli.ResolveOrReportcatch onlyMetaModelException/MetadataLocationException, sodotnet meta gen/verify/fmtcrash with a stack trace. The user does not get theRefuse(...)message that namescsharp.providers, as the other bad-entry cases do. Fix: wrap theGetAssemblyName/LoadFromAssemblyPath/GetTypecalls and convertBadImageFormatException,FileLoadExceptionandReflectionTypeLoadExceptiontoRefuse(spec, ...).server/typescript/packages/cli/src/commands/verify.ts:331- Simplification: the new early return at verify.ts:315-321 (forgeConfig === undefined && existsSync(config)returns 2) makes the oldelse if (existsSync(join(genConfigDir, "metaobjects.config.ts")))branch at verify.ts:331-336 unreachable. In that branchforgeConfigis undefined, so the config file cannot exist there. Remove the dead branch and leaveelse say(...).server/python/tests/codegen/test_cli_config_providers.py:105- The new Python fmt tests use weak oracles.test_fmt_rewrites_a_file_using_the_config_vocabularyasserts"@searchable" in text or "searchable" in text: the second clause makes the first redundant, and a formatter that drops the@sigil still passes.test_fmt_honors_config_providersacceptsrc in (0, 1).test_fmt_without_config_providers_still_refuses_the_vocabularyaccepts any non-zero exit, including a crash.test_fmt_merges_config_and_flag_providersuses metadata that does not need the flag provider, so it does not prove the merge. Stronger oracles: compare the formatted file to the shared fixturefixtures/conformance/provider-custom-vocabulary-success/expected.json, assert the specificERR_UNKNOWN_SUBTYPEin stderr for the no-config case, and make the merge test's metadata need both providers (the verify test already does this withvalidator.geocheck).server/typescript/packages/cli/src/commands/upgrade.ts:136-meta upgrade <projectRoot>looks up providers withloadProjectProviders(cwd, projectRoot), so the nearest-ancestor walk starts from cwd and not from the project named on the command line. Runningmeta upgrade sub/projfrom a directory that has its ownmetaobjects.config.tsuses that other project's providers.fmt-enginestarts its walk atopts.projectRoot, which is consistent with the argument. UseloadProjectProviders(projectRoot, projectRoot)here. The impact is small: upgrade is best-effort, and the providers only add abstract-anchor types.server/csharp/MetaObjects/Config/MetadataLocation.cs:85- Inline fix: the newLoadProvidersmethod had been inserted betweenProjectRootFor's<summary>andProjectRootFor, so the summary was attached to the wrong member. I gaveLoadProvidersits own one-line summary and put the original summary back onProjectRootFor. No code changed.server/java/metadata-ktx/src/main/kotlin/com/metaobjects/metadata/ktx/Loader.kt:28- Inline fix: theloadDirectoryKDoc said providers are composed "sealed, strict".MetaDataLoader.fromDirectorybuilds its loader withLoaderOptions.create(false, false, false), which is lax. I changed the comment to say the registry is sealed and the directory load stays lax, as it is without providers.🔧 Fix applied.
2 infos still open:
server/csharp/MetaObjects/Config/MetadataLocation.cs:85- Inline fix: the newLoadProvidersmethod had been inserted betweenProjectRootFor's<summary>andProjectRootFor, so the summary was attached to the wrong member. I gaveLoadProvidersits own one-line summary and put the original summary back onProjectRootFor. No code changed.server/java/metadata-ktx/src/main/kotlin/com/metaobjects/metadata/ktx/Loader.kt:28- Inline fix: theloadDirectoryKDoc said providers are composed "sealed, strict".MetaDataLoader.fromDirectorybuilds its loader withLoaderOptions.create(false, false, false), which is lax. I changed the comment to say the registry is sealed and the directory load stays lax, as it is without providers.✅ **Test** - passed
✅ No issues found.
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsLive TS:meta exportin a disposable project withproviders: [forgeTypesProvider]in metaobjects.config.ts → exit 0, no ERR_PROVIDER_DUPLICATE_ID (export duplicate fix)Live TS:meta exportwith a project provider registeringlayout.picker+ field attrs@searchable/@valueLabels→ exit 0, attrs round-trip into canonical JSONLive TS: same metadata without the provider in config → exit 1Unknown type "layout.picker" — not registered(refusal path)Live C#:dotnet run --project server/csharp/MetaObjects.Cli -- verifywith csharp.providers pointing at a non-assembly file → exit 2, message names csharp.providers and BadImageFormatException (no stack trace)dotnet test MetaObjects.Cli.Tests --filter CsharpProvidersTests→ 19 passed (test harness, not live)python3 -m pytest tests/codegen/test_cli_config_providers.py→ 6 passed (in-process, not live)python3 -m pytest tests/conformance→ 1417 passed (conformance runner, not live)mvn -o test -pl metadata -am -Dtest=LoaderProvidersTest,ConformanceTest,YamlTwinConformanceTest→ BUILD SUCCESS (not live)mvn -o test -pl metadata-ktx,maven-plugin,core-spring -am -Dtest=ProviderVocabularyTest,MojoProvidersTest,MetaDataLoaderProvidersTest→ 3+7+2 passed, BUILD SUCCESS (not live)bun test packages/cli/test/config-providers-everywhere.test.ts packages/sdk/test/config.test.ts(server/typescript) → 45 pass, 0 fail (not live)Baselinescripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains(ran by harness, green)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
no-mistakes gate: 1 run. Review raised 5 fixable findings (a
meta exportduplicate-provider failure, an unhandled bad C# provider assembly, ameta upgradeconfig-walk start, a deadmeta verifybranch, weak Python fmt test oracles), all fixed in that run.