fix: shared enum — reject conflicting @values across ports + Kotlin codegen fixes - #260
Merged
Conversation
…x + extends/values load error) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
A field.enum that both extends a shared package-level abstract enum AND declares its own @values silently dropped the own set under the shared-enum codegen collapse. Mirrors the TS reference: fire ERR_ENUM_EXTENDS_VALUES_CONFLICT only when the resolved super is abstract AND root-level (parent.type == metadata) — a concrete super or a non-root abstract super stays legal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
…covers Kotlin) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
…alues conflict The error fixture pins ERR_ENUM_EXTENDS_VALUES_CONFLICT with an exact source.jsonPath on the offending field.enum; the negative fixture proves a field.enum extending a CONCRETE (non-root) enum with its own @values loads clean. Verified across all four ports (TS 506, Python 366, Java 506, C# 827) — identical jsonPath, negative loads with zero errors under strict. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
A field.enum inheriting @values through TWO extends hops (projection.status -> entity.status -> shared-abstract enum) generated no per-projection enum at all, so every consumer of that column failed to resolve the (absent) type. Root cause: KotlinTypeMapper.enumTypeName decided "collapse onto the shared enum" from the TOP-MOST super, so a projection field walked past its concrete entity super to the root abstract enum and wrongly collapsed. Values were never the issue — getMetaAttr(@values) is inheritance-aware across any number of hops. Fix: key the collapse decision on the IMMEDIATE super — only a field whose direct extends target is a package-level abstract enum (no declaring object) collapses onto the shared type; a field whose direct super is a concrete entity/projection field gets its own <Object><Field> enum, populated with the depth-inherited members. Byte-identical for one-hop projections, FR-019 shared enums, and entity-extends-shared (codegen-kotlin 310/310). Also hardens KotlinEnumEmitter: a field.enum resolving to no @values now fails loudly at generate-time (error) instead of silently emitting no file — the exact silent no-emit #259 reported. Sibling of #246. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
Pins cross-port that a field.enum inheriting @values through TWO extends hops (projection.status -> entity.status -> shared-abstract enum) resolves its effective member set and loads clean under strict. Happy-path fixture; verified on all four ports (TS 506+, Python 366+, Java 508, C# 829). Complements the codegen-kotlin two-hop compile-gate that guards the Kotlin-codegen naming bug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
…-package enums Adds an [Unreleased] CHANGELOG entry covering #246 (cross-package shared-enum import + ERR_ENUM_EXTENDS_VALUES_CONFLICT) and #259 (two-hop projection enum resolution + loud no-values guard). Documents in field-types.md how to share one enum across packages via an abstract field.enum + extends, the two enforced rules (no own @values on a shared-enum child; members resolve through any number of extends hops), and links the two new conformance fixtures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
…re counts - field-types.md: drop the 'extend a concrete enum for an independent set' recommendation (steered users into a shape Kotlin's collapse mishandles); guide to a separate field.enum instead. - CHANGELOG: reword the closing note (the C# materialized-enum + enum-PK items are recorded in the design spec, not open trackers) and note the Kotlin isAbstract-leg follow-up. - CONFORMANCE.md + CLAUDE.md: metamodel conformance fixture count 255 -> 258. - design spec: record the pre-existing Kotlin enumTypeName isAbstract-leg divergence as an out-of-scope follow-up (surfaced by the Fable review). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLoJkFSyoticveo5ehMUAr
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.
Intent
Branch fix/246-shared-enum-cross-package: two sibling shared-enum issues for a coordinated PATCH release (npm/PyPI/NuGet 0.20.11, Maven 7.11.8).
#246: (A) Kotlin KotlinExposedTableGenerator emits the cross-package import for a shared field.enum (was Unresolved reference), vanilla + TPH-fold; byte-identical for same-package (import only when enum packageName non-empty AND != table package). (B) NEW cross-port loader error ERR_ENUM_EXTENDS_VALUES_CONFLICT: a field.enum that both extends a shared package-level ABSTRACT enum AND declares its own @values now fails to load (was silently dropped). Identical in all four loaders (TS attr-schema-validate.ts, Python validation_passes.py, Java ValidationPhase.java, C# ValidationPasses.cs; Kotlin inherits JVM loader): fires iff OWN @values present AND resolving immediate super is abstract AND that super's parent is the metadata root. Deliberately a new load-rejection of a previously-silently-accepted INVALID model (a correction, not a break; valid models byte-identical). Registered in all six error-ledger files. Gated by conformance fixtures error-enum-extends-values-conflict (exact cross-port jsonPath) + enum-extends-concrete-with-own-values (negative/legal).
#259 (Kotlin codegen, sibling): KotlinTypeMapper.enumTypeName keys the shared-enum collapse on the IMMEDIATE super (field.superField.declaringObject == null) not the top-most, so a two-hop projection->entity->shared-abstract enum materializes its own per-projection enum (was generating nothing). getMetaAttr is inheritance-aware multi-hop, so values were never the issue. Hardens KotlinEnumEmitter: a field.enum resolving to no @values fails loudly at generate-time. Byte-identical for one-hop projections, FR-019 shared enums, entity-extends-shared. Gated by a two-hop KotlinCompilation compile-gate + a depth-2 conformance fixture.
Docs: CHANGELOG [Unreleased], field-types.md/abstracts-and-inheritance.md enum guidance, CONFORMANCE.md/CLAUDE.md fixture count 258.
Already passed two review rounds (focused #259 = APPROVED; Fable whole-branch = SHIP, doc fixes applied). Green on all four ports: TS conformance 508, Python 367, Java ConformanceTest 508, C# 829, codegen-kotlin 310. NOTES: the new load-rejection is deliberate (not an over-reject bug); byte-identity for valid models is the guardrail; a pre-existing Kotlin isAbstract-leg divergence is a documented out-of-scope follow-up (NOT in this PATCH).
What Changed
ERR_ENUM_EXTENDS_VALUES_CONFLICT: afield.enumthat both extends a shared package-level abstract enum AND declares its own@valuesnow fails to load in all four ports (TS/Python/Java/C#; Kotlin inherits the JVM loader) — previously this invalid model was silently dropped. Registered in all six error-ledger files; gated by theerror-enum-extends-values-conflictandenum-extends-concrete-with-own-valuesconformance fixtures.KotlinExposedTableGeneratornow emits the cross-package import for a sharedfield.enum(vanilla and TPH-fold paths), andKotlinTypeMapperkeys shared-enum collapse on the immediate super so a two-hop projection→entity→shared-abstract enum materializes its own enum instead of generating nothing. Same-package output is byte-identical;KotlinEnumEmitternow fails loudly at generate-time when a resolved enum has no@values.Risk Assessment
✅ Low: Both Kotlin fixes are correct, byte-identity-guarded, and compile-gated; the new cross-port load-rejection is deliberate, documented, gated by an exact-jsonPath fixture, and only rejects an INVALID model (the legal non-abstract-super case is pinned) — the sole observation is a benign, untested, degenerate edge-case divergence in secondary-error count that leaves the model failing on all ports.
Testing
Ran the smallest relevant per-port tests (not full suites): all four loader ports for the new ERR_ENUM_EXTENDS_VALUES_CONFLICT (TS/Python/Java/C#, each 3 tests pinning the precise conflict-vs-legal boundary), the TS cross-port conformance corpus (508/0, covering the jsonPath error fixture and the legal negative), and the two Kotlin codegen compile-gates for #246(A) cross-package import and #259 two-hop projection enum. Every targeted test passed; reproduced the generated cross-package Kotlin source to confirm the import is emitted only when the enum's package differs from the table's (byte-identity guardrail) and that the two-hop projection now materializes its own enum class. Captured two evidence artifacts; working tree left clean after removing the throwaway dump test. No actionable issues found.
Evidence: Generated cross-package enum table source (#246(A))
Generated Exposed table classes. OrderTable.kt (pkg acme.orders) and ShipmentTable.kt (pkg acme.shipping, TPH-fold base) now emitimport acme.common.RecordStatusand referenceRecordStatus::classin enumerationByName; same-package ShipmentCarrier correctly gets NO import (byte-identity guardrail). Before the fix this import was absent -> Unresolved reference compile error.Evidence: Cross-port enum test results (#246 + #259)
Consolidated results: ERR_ENUM_EXTENDS_VALUES_CONFLICT green on all 4 loaders (TS/Python/Java/C#, 3 tests each, identical three-way boundary); TS conformance 508/0; #259 KotlinProjectionTwoHopEnumTest materializes ProgramViewStatus (projection's own enum); #246(A) KotlinExposedTableCrossPackageEnumTest compile-gate green.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
server/python/src/metaobjects/loader/validation_passes.py:578- Narrow cross-port divergence in the EMPTY-@values edge case for the new ERR_ENUM_EXTENDS_VALUES_CONFLICT check: when a field.enum declares an empty@values: []AND extends a shared abstract root enum, TS (attr-schema-validate.ts:316/357) and C# (ValidationPasses.cs:2292/2329) emit BOTH ERR_BAD_ATTR_VALUE(empty) and ERR_ENUM_EXTENDS_VALUES_CONFLICT, because their empty-check pushes-and-falls-through to the unconditional Kotlin codegen: generated *Table / *RepositoryBase reference a cross-package shared field.enum without importing it #246 check. Python short-circuits withcontinueat validation_passes.py:578, and Java throws eagerly inside validateEnumValues at ValidationPhase.java:518 before reaching the Kotlin codegen: generated *Table / *RepositoryBase reference a cross-package shared field.enum without importing it #246 check — so both emit only ERR_BAD_ATTR_VALUE. The intent's stated contract ('fires iff OWN @values present AND resolving immediate super is abstract AND that super's parent is the metadata root') is technically better honored by TS/C# (an empty array IS a present attr); Python/Java never reach the check for empty arrays. This is benign — the model still fails to load on all four ports, it is not gated by any conformance fixture (theerror-enum-extends-values-conflictfixture uses non-empty @values where all four ports agree exactly), and the scenario (empty values array on a field that also extends a shared enum) is semantically degenerate. Noting only because the intent asserts strict cross-port identity; no valid model and no gated scenario is affected.✅ **Test** - passed
✅ No issues found.
TS loader:cd server/typescript && bun test packages/metadata/test/enum-extends-values-conflict.test.ts-> 3 pass / 0 failPython loader:PYTHONPATH=server/python/src python3 -m pytest server/python/tests/loader/test_enum_extends_values_conflict.py-> 3 passedJava/JVM loader (Kotlin inherits):mvn test -pl metadata -Dtest=EnumExtendsValuesConflictTest -o-> Tests run: 3, Failures: 0, Errors: 0C# loader:dotnet test server/csharp/MetaObjects.Conformance.Tests/MetaObjects.Conformance.Tests.csproj --filter ~EnumExtendsValuesConflictTests-> Passed: 3, Failed: 0, Total: 3TS cross-port conformance:cd server/typescript && bun test packages/metadata/test/conformance.test.ts-> 508 pass / 0 fail (includes error-enum-extends-values-conflict jsonPath + enum-extends-concrete-with-own-values legal fixtures)#259 Kotlin two-hop compile-gate:mvn test -pl codegen-kotlin -Dtest=KotlinProjectionTwoHopEnumTest -o-> Tests run: 1, Failures: 0; generated ProgramViewStatus.kt/.class (projection's own enum) compiles#246(A) Kotlin cross-package compile-gate:mvn test -pl codegen-kotlin -Dtest=KotlinExposedTableCrossPackageEnumTest -o-> Tests run: 1, Failures: 0; full generated tree compiles (ExitCode.OK)Evidence capture: throwaway dump test reproduced generation of OrderTable/InvoiceTable/ShipmentTable/RecordStatus.kt provingimport acme.common.RecordStatuspresent and same-package ShipmentCarrier import absent — test removed, tree verified cleanls -d fixtures/conformance/*/ | wc -l, so the number can't silently diverge again.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.