Skip to content

feat(metadata): add one physical naming setting read by every port - #441

Merged
dmealing merged 23 commits into
mainfrom
fm/mo-1-1-0-rc4-fixes
Oct 17, 2026
Merged

dmealing merged 23 commits into
mainfrom
fm/mo-1-1-0-rc4-fixes

Conversation

@dmealing

@dmealing dmealing commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • Adds a naming block to .metaobjects/config.json (columns: snake_case | literal | kebab-case; tables: snake_case | literal). Every port's codegen, meta migrate, runtime ObjectManager and verify read it from the loaded model. Both rules default to snake_case, which changes generated column names in C#, Java, Kotlin's runtime and Python unless the project sets "columns": "literal".
  • Former per-port naming settings (columnNamingStrategy, --column-naming, the columnNaming pom arg, ObjectManager(column_naming=), SimpleMappingHandlerDB.setColumnNaming) are refused with a message that names the new setting and the migration guide. Engine functions that took the rule as an option now read it from the model.
  • verify --codegen (Python, C#) reports when committed output was generated under a different column or table rule and names the naming setting that reproduces it. Adds the fixtures/naming-conformance/physical-names/ corpus run by every port, and docs/features/migrations/physical-naming.md. Also adds requires checking to the Maven metaobjects:generate goal and fixes the prerelease tooling scripts.

Validation runs and size

  • Runs on this branch: 4. Run 1 was aborted by decision to fold in a scope change. Run 2 failed on a model-provider outage. Run 3 was aborted when all work was paused, not on a verdict. Run 4, this one, passed. It ran with --skip ci because CI is local for this repository.
  • Size: git diff --shortstat main...HEAD gives 329 files changed, 7071 insertions(+), 1752 deletions(-). Most of that is regenerated docs, fixtures and the five-port naming corpus.
  • Run 4 review fixes (commit d74f3b8cd): scripts/prerelease.mjs now runs each build and publish step as an awaited child in its own process group. SIGINT, SIGTERM and SIGHUP kill the active step, restore the version files and exit. Before, a SIGTERM during a synchronous publish waited for the whole script and publishing went on. Also fixed: the repin message in tools/prerelease/prerelease-link.sh prints only when the manifest changed, a stale Python comment, and a test for the Java Mustache table-name helper. Known tradeoff, accepted: a signal the script does not handle, sent to the whole process group (for example a group SIGKILL), no longer reaches the running step, which then finishes. No later step starts.
  • Maven requires check, real mvn metaobjects:generate in a scratch project (private -Dmaven.repo.local). This was captured on an earlier head of this branch. At that head the refusal named only direct companions. The current head names the transitive closure, which GeneratorRequiresCompileTest covers and which passed in run 4.
    • SpringControllerGenerator alone: exit 1, nothing written: SpringControllerGenerator ("routes") needs "dto" (SpringDtoGenerator), "repository" (SpringRepositoryGenerator), "filter-allowlist" (SpringFilterAllowlistGenerator) in the same <generators>: its output references what those generators write, so without it the generated code does not compile.
    • With -Dmetaobjects.allowUnmetRequires=true: exit 0, the same text as a warning, 40 controllers written.
  • Noted, not changed: for an entity that declares no source, the C# entity generator's table name falls back to the bare entity name instead of applying naming.tables. Such an entity has no table, so the name has no effect.
Validation

Risk Assessment

✅ Low: The fix round fixes all four summary items, and each fix is bounded. Prerelease steps now run as awaited, group-killable children; a probe under bun confirmed they stop on SIGTERM. The repin message is now conditional, the stale comment is corrected, and the Mustache helper is tested against the shared naming corpus. The only remaining note is an informational tradeoff for signals the script does not handle.

Testing

Live checks: Python gen with literal and default naming, and the removed --column-naming flag refused on gen and verify (exit 2). Script, conformance and JUnit suites for prerelease, naming, TS refusals and the Java requires closure pass but were not live product runs. Quick CI exited 0 and skipped the csharp/java/python/kotlin conformance lanes, java-reactor, integration-tests and the cube live check, so those remain unverified.

  • Live validation: ✅ go - 3 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Python gen literal naming emits camelCase column names ✅ pass live live python gen in disposable project
Python gen default naming emits snake_case column names ✅ pass live live python gen in disposable project
Removed --column-naming refused on gen and verify ✅ pass live live CLI exit 2 with shared message
Prerelease version, pagination, SIGTERM restore (P1-P3) ⏸️ untested no Prior payload ran only node scripts/test-prerelease-version.mjs in script tests with no live registry; no live product run was recorded.
Prerelease polyglot link/unlink (P4) ⏸️ untested no Prior payload ran only tools/prerelease/test-prerelease-link.sh in temp repos; no live product run was recorded.
Shared naming conformance corpus in TS, Python, C#, Java ⏸️ untested no Prior payload ran per-port conformance tests, which are not live product runs; no live run was recorded.
Java maven-plugin requires closure refuses missing companions ⏸️ untested no Prior payload ran a JUnit proof (GeneratorRequiresCompileTest), not a live mojo run; no live run was recorded.
Quick regression suite ⏸️ untested no Prior payload ran scripts/ci-local.sh --quick, which skipped 4 lanes; no live product run was recorded.

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info

✅ No issues found.

🔧 Fix applied.
1 info still open:

  • ℹ️ scripts/prerelease-version.mjs:106 - Tradeoff introduced by the round-1 fix (review-summary-1 item 1). run now spawns each step with detached: true. On Linux that calls setsid, so every build or publish step runs in its own session. The three handled signals (SIGINT, SIGTERM, SIGHUP) are covered: the handler SIGKILLs each active group, restores the files and exits. I checked this under bun 1.3.14, the runtime prerelease:publish actually uses. A signal sent to the whole process group that the script does not handle no longer reaches the step. Examples: SIGQUIT from Ctrl-, kill -9 -&lt;pgid&gt;, or a supervisor that escalates to a group SIGKILL. In those cases only node dies, the current bun publish or mvn deploy keeps running to completion, and the version files stay bumped. With execSync, the step shared the group and died with it. This needs no action: the handled signals are the realistic paths, and a group kill still stops every later step.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Python gen literal naming emits camelCase column names ✅ pass live live python gen in disposable project
Python gen default naming emits snake_case column names ✅ pass live live python gen in disposable project
Removed --column-naming refused on gen and verify ✅ pass live live CLI exit 2 with shared message
Prerelease version, pagination, SIGTERM restore (P1-P3) ⏸️ untested no Prior payload ran only node scripts/test-prerelease-version.mjs in script tests with no live registry; no live product run was recorded.
Prerelease polyglot link/unlink (P4) ⏸️ untested no Prior payload ran only tools/prerelease/test-prerelease-link.sh in temp repos; no live product run was recorded.
Shared naming conformance corpus in TS, Python, C#, Java ⏸️ untested no Prior payload ran per-port conformance tests, which are not live product runs; no live run was recorded.
Java maven-plugin requires closure refuses missing companions ⏸️ untested no Prior payload ran a JUnit proof (GeneratorRequiresCompileTest), not a live mojo run; no live run was recorded.
Quick regression suite ⏸️ untested no Prior payload ran scripts/ci-local.sh --quick, which skipped 4 lanes; no live product run was recorded.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains (baseline, green)
  • node scripts/test-prerelease-version.mjs: all pass
  • bash tools/prerelease/test-prerelease-link.sh: all pass
  • bun test test/physical-naming-conformance.test.ts (sdk): 11 pass
  • uv run pytest tests/conformance/test_naming_conformance.py tests/unit/test_column_naming_strategy.py tests/codegen/test_config_run_settings.py: 33 pass
  • dotnet test MetaObjects.Codegen.Tests (NamingConformanceTests, GeneratorListEntryTests, AgentContextStalenessTests): 12 pass
  • mvn -q test -pl metadata -Dtest=PhysicalNamingConformanceTest: 5 pass
  • bun test gen-docs-generators.test.ts and migrate-config-dir.test.ts (cli): 30 pass
  • bun test physical-naming-refusals.test.ts and metaobjects-config.test.ts (codegen-ts): 15 pass
  • mvn -q test -pl maven-plugin -am -Dtest=GeneratorRequiresCompileTest: exit 0
  • scripts/ci-local.sh --quick: passed, 4 lanes skipped
  • LIVE python gen: literal naming yields firstName, default yields first_name
  • LIVE python gen/verify with --column-naming: exit 2 with shared removal message
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…the rc.3 application-build test found

An agent following the shipped 1.1.0 skills and the migration guide got wrong output or
broken builds in four places. Each gets its root-cause fix:

- Python and C# declare the run's column naming (and Python its template directory) in the
  generator list: `columnNaming` / `templates` in metaobjects.config.yaml, `ColumnNaming` on
  a C# GeneratorList. gen and verify --codegen read them, a contradicting flag is refused,
  and verify names a column-naming mismatch instead of advising a regeneration.
- The Java and Kotlin skill pom examples list the whole REST tier, and the Maven plugin
  refuses a list whose generators' requires are not declared. Every requires closure and
  both examples are compiled over the fitness corpus in tests; that found the Kotlin
  extractor's dependency on the parser tier.
- Every docs example gives each port its own docs directory, with the reason.
- Every removal message and skill names the migration guide by its full public URL.

Also: C# --list prints each generator's Generators.cs expression and options; the C#
staleness notice ignores build metadata; meta verify --docs prints once; Python
verify --templates reads the config; the skills stop asserting metaobjects/ is where
metadata lives; prerelease:publish reads "released" from npm and the tags, refuses to skip
an unpublished CHANGELOG section, pages the iteration counter and restores on SIGTERM;
prerelease-link.sh handles nested per-port projects and the .NET tool manifest.
…a directory

The removal messages now name the migration guide by its GitHub URL, whose repository
segment the hardcoded-directory guard read as the default metadata directory. The guard
skips that one form, and a pinned case keeps the exemption that narrow.
… sits, and its plugin dependencies

The block-buffer flushed a <plugin> at its first <configuration> or <executions>, so a
<version> declared after them was never repinned, and a plugin's own <dependencies> were not
read at all. Each <dependency> or <plugin> is now read whole, and only its own <version> moves.
…p, one npm fetch and shared helpers

simplify-qwen pass over the branch. Behaviour unchanged: C# CLI 297 and Codegen 844 tests,
maven-plugin 217, Python codegen 892, and the prerelease-link and prerelease-version suites
pass on it. Its foreign-roots cache was dropped: project_find runs in subshells, so the
array never persisted.
…ference alone

Walking every pom reached modules that reference the parent's property; the awk rewrote that
reference to a literal, and unlink never restored it.
…ts conformance corpus

The naming block of .metaobjects/config.json names the column and table rules; metadata
overrides win, both rules default to snake_case in every port, and every former per-port
source refuses. The two corpora that pin a committed literal-column schema declare it.
…n guide and the skills (ADR-0058)

Column and table names the metadata does not give come from naming in
.metaobjects/config.json, carried by the loaded root: meta migrate, codegen, the
ObjectManager and verify all read it there. columnNamingStrategy in metaobjects.config.ts,
on ObjectManager and on agentDocsFile() is refused with the setting and the guide; meta
migrate and meta verify refuse it too instead of falling back to the default. The engine
options that carried the rule read the model instead. The TypeScript harness gives each
corpus its naming from the corpus's own .metaobjects/config.json, and the physical-names
corpus runs through the sdk config reader and the shared resolvers.
NeutralConfig parses naming from .metaobjects/config.json; the project-aware load sets it on
the root, and every resolver (codegen, EF mapping, names, verify, the table rule in
MetaSource.PhysicalName) reads it there. --column-naming is refused on gen and verify; the
unreleased GeneratorList.ColumnNaming is removed and init scaffolds the plain list again.
The default is snake_case. The physical-names corpus runs through NeutralConfig and
CSharpNaming. The persistence corpus's naming file moves above canonical/, where directory
loaders no longer read it as metadata.
…aming their own DDL uses

They provision their tables in snake_case by hand, while the api-contract corpus declares
literal columns for the reference servers; each lane now sets the root to the rule its DDL
was written in, as the report lanes do. The whole integration suite passes on it.
The neutral config reader parses naming; the project-aware load sets it on the root, and
codegen (the *_names.py generator included), the ObjectManager, verify and the table rule
in MetaSource.physical_name read it there. A model loaded outside the project gets it with
attach_naming(root, project_dir). --column-naming, ObjectManager(column_naming=) and the
release candidates' columnNaming key are refused; the default is snake_case. The
physical-names corpus runs through the neutral config reader and the shared resolvers.
…DR-0058)

NeutralConfig parses naming; the Maven plugin's load and the Spring loader
(metaobjects.physical-naming-config) set it on the root, and ColumnNaming.resolve and
MetaSource.getPhysicalName read it there, so Java codegen, Kotlin codegen, the names
generators, OMDB and the mustache helpers resolve the same names. The columnNaming
generator arg and SimpleMappingHandlerDB.setColumnNaming are refused; the default is
snake_case. The mustache dbColumn/dbTable helpers, which derived names on their own, now
use the shared resolvers. The physical-names corpus runs through NeutralConfig.
The registry prose for @column and @table still said the default name came
"via columnNamingStrategy", a setting 1.1.0 removes. They now name the
naming.columns / naming.tables rule in .metaobjects/config.json (ADR-0058).
Regenerated the embedded TS definition, expected-registry.json, the
metamodel docs fixture and the reference pages. Prose only, so
metamodelVersion does not move. The codegen-ts README's naming paragraph
also pointed at the removed setting and at @dbColumn, which is @column.
verify --codegen in C# and Python reported a naming mismatch only when
another column rule reproduced the committed output. Output committed under
the other naming.tables rule fell through to "regenerate", which binds the
code to a table the database does not have. Both ports now try every other
combination: another column rule alone, then another table rule alone, then
both together, and print the same message naming the full naming block that
reproduces the committed tree. No new setting or flag; the table rule reaches
codegen through the loaded root, so each probe sets the candidate there and
restores it.
…ndidate

The probe re-pointed the already-generated root at each candidate naming.
A loaded tree caches the table name it resolved (MetaObject.DbTable), so
with only the entity generator selected, whose [Table(...)] carries that
name, a table-only mismatch still read as drift and verify advised a
regenerate. Each candidate now regenerates from its own load. Also: the
ADR-0058 status says expected-registry.json changes in prose only.
@dmealing
dmealing merged commit f6c001e into main Oct 17, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-1-1-0-rc4-fixes branch October 17, 2026 05:25
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