Skip to content

feat(java): make entity and extractor generators ejectable - #425

Merged
dmealing merged 6 commits into
mainfrom
fm/mo-1-1-0-ejectable-java
Oct 11, 2026
Merged

dmealing merged 6 commits into
mainfrom
fm/mo-1-1-0-ejectable-java

Conversation

@dmealing

@dmealing dmealing commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • mvn metaobjects:eject -Dnames=entity,extractor -Dport=java now copies the Java entity generator (seven files, including the class-body writers) and the extractor generator into the adopter's codegen/ module. GeneratorRegistry gains GeneratorInfo.ejectWith(), and the EmitsObjectExtractors marker plus ARG_EMIT_EXTRACTORS let extractor run as its own <generator> with emitExtractors=false set on entity.
  • Java entity no longer writes the ObjectCodeWriter: and Generated On: header lines, so regenerated class files change once in the header comment and mvn metaobjects:verify no longer depends on the clock.
  • mvn metaobjects:eject -Dnames=entity|extractor with neither or both of metaobjects-codegen-spring and metaobjects-codegen-kotlin declared now refuses with an ambiguous-port error and asks for -Dport=java|kotlin, instead of silently resolving to Kotlin.

Gate runs

One validation run. It took three review fix rounds and one lint re-run: the first lint attempt was terminated by host load, with no gate failing an assertion.

Before merge

  • The pipeline's test step runs the TypeScript lanes only. The Java side was run separately on the pushed head f297a6d8: Java and Kotlin conformance, the full Maven reactor and both integration lanes pass.
  • fm/mo-1-1-0-pl-fixes also removes the Generated On: header line from the same writer, so whichever of the two lands second needs a rebase over that hunk.
  • ADR-0034 "Amendment 6" assumes the importable-stock-generators change lands Amendment 5 first. Renumber at rebase if the order differs; the number is also cited in code comments and docs.
Validation

Risk Assessment

✅ Low: No defect found in a full pass: the three fix rounds landed as decided, existing poms change only in the documented header comment, and the one residual is that this pipeline's test step runs TypeScript lanes only, so the Java changes rest on the fix rounds' local mvn runs.

Testing

Java side driven live: built and installed the plugin, then ran generate, verify, eject, owned-writer edit, and compile against a disposable two-entity Maven project. All scenarios passed, including the adversarial drift check. Java module tests and SDK agent-context goldens pass. Disposable project and local m2 artifacts removed; evidence logs saved. Overall: go.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Packaged generate with entity and extractor entries writes exactly one <Name>Extractor per entity (no duplicate emitter) ✅ pass live 01-generate-packaged.log; target/generated-sources/java/live/shop/{Product,Order}Extractor.java
Generated output is byte-identical across a second wall-clock boundary and has no 'Generated On' timestamp line ✅ pass live diff -r of two generate runs 2s apart returned no differences; grep 'Generated On' count 0
mvn metaobjects:verify passes on an unchanged model ✅ pass live 02-verify-unchanged.log: 'No codegen drift detected across 2 generator(s)'
Adversarial: hand-edited generated file is caught by verify ✅ pass live 03-verify-tampered-fails.log: BUILD FAILURE, [content-differs] Product.java
mvn metaobjects:eject -Dnames=entity,extractor copies the generator and every writer it emits through ✅ pass live 04-eject-entity-extractor.log: 7 owned files ejected including BaseObjectCodeWriter, JavaCodeWriter, PojoAwareCodeWriter, ValueObjectCodeWriter
Owned generator output is byte-identical to packaged generator output ✅ pass live diff -r of owned-pom generate vs packaged generate: identical (6 files)
Editing an owned writer changes the generated class body ✅ pass live 05-generate-owned-with-writer-edit.log; marker OWNED-EDIT-MARKER present in Product.java and Order.java after rebuild and regenerate
Generated code compiles with packaged and owned generators ✅ pass live mvn -o compile exit 0 on both disposable projects
Explicit emitExtractors=true on entity alongside an extractor entry still yields the same output ✅ pass live generate exit 0 and diff -r against baseline output identical. Limitation: both paths write the same file path, so a duplicate write would be invisible here; the derivation itself is covered by the Ja…
Evidence: Java module build and tests log
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] MetaObjects Core                                                   [pom]
[INFO] MetaObjects :: MetaData                                            [jar]
[INFO] MetaObjects :: Render                                              [jar]
[INFO] MetaObjects :: ObjectManager                                       [jar]
[INFO] MetaObjects :: Code Generation :: Base                             [jar]
[INFO] MetaObjects :: Code Generation :: Mustache                         [jar]
[INFO] MetaObjects :: ObjectManager :: RDB                                [jar]
[INFO] MetaObjects :: Codegen :: Spring                                   [jar]
[INFO] MetaObjects :: Metadata Kotlin Facade                              [jar]
[INFO] MetaObjects :: Codegen :: Kotlin                                   [jar]
[INFO] MetaObjects :: Code Generation :: PlantUML                         [jar]
[INFO] MetaObjects :: Maven Plugin                               [maven-plugin]
[INFO] 
[INFO] --------------------< com.metaobjects:metaobjects >---------------------
[INFO] Building MetaObjects Core 8.1.0                                   [1/12]
[INFO]   from pom.xml
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] --- jacoco:0.8.13:prepare-agent (prepare-agent) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] argLine set to empty
[INFO] 
[INFO] --- jacoco:0.8.13:report (report) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:prepare-agent-integration (prepare-agent-integration) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:report-integration (report-integration) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:report-aggregate (report-aggregate) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:check (coverage-check) @ metaobjects ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- install:3.1.3:install (default-install) @ metaobjects ---
[INFO] Installing ~/.no-mistakes/worktrees/5b8c6c97e760/01M4NK4YJV84WSN9JNKQPFG7W4/server/java/pom.xml to ~/.m2-ci/repository/com/metaobjects/metaobjects/8.1.0/metaobjects-8.1.0.pom
[INFO] 
[INFO] ----------------< com.metaobjects:metaobjects-metadata >----------------
[INFO] Building MetaObjects :: MetaData 8.1.0                            [2/12]
[INFO]   from metadata/pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[WARNING] The artifact xml-apis:xml-apis:jar:2.0.2 has been relocated to xml-apis:xml-apis:jar:1.0.b2
[INFO] 
[INFO] --- jacoco:0.8.13:prepare-agent (prepare-agent) @ metaobjects-metadata ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] argLine set to empty
[INFO] 
[INFO] --- resources:3.3.1:copy-resources (bundle-spec-metamodel) @ metaobjects-metadata ---
[INFO] Copying 19 resources from ../../../spec/metamodel to target/classes/spec/metamodel
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ metaobjects-metadata ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] 
[INFO] --- compiler:3.13.0:compile (default-compile) @ metaobjects-metadata ---
[INFO] Nothing to compile - all classes are up to date.
[INFO] 
[INFO] --- resources:3.3.1:testResources (default-testResources) @ metaobjects-metadata ---
[INFO] Copying 25 resources from src/test/resources to target/test-classes
[INFO] 
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ metaobjects-metadata ---
[INFO] Nothing to compile - all classes are up to date.
[INFO] 
[INFO] --- surefire:3.5.2:test (default-test) @ metaobjects-metadata ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.metaobjects.registry.CreateWithCoreProvidersTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.932 s -- in com.metaobjects.registry.CreateWithCoreProvidersTest
[INFO] Running com.metaobjects.registry.ComposeRegistryTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.metaobjects.registry.ComposeRegistryTest
[INFO] Running com.metaobjects.registry.CamelCaseSubtypeRoundTripTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.212 s -- in com.metaobjects.registry.CamelCaseSubtypeRoundTripTest
[INFO] Running com.metaobjects.registry.UnifiedRegistrySchemaIntegrationTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 s -- in com.metaobjects.registry.UnifiedRegistrySchemaIntegrationTest
[INFO] Running com.metaobjects.registry.BaseSubTypeAnalysisTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in com.metaobjects.registry.BaseSubTypeAnalysisTest
[INFO] Running com.metaobjects.registry.ChildRequirementCaseTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in com.metaobjects.registry.ChildRequirementCaseTest
[INFO] Running com.metaobjects.registry.ObjectClassRegistryGlobalTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in com.metaobjects.registry.ObjectClassRegistryGlobalTest
[INFO] Running com.metaobjects.registry.RegistryBootstrapTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.014 s -- in com.metaobjects.registry.RegistryBootstrapTest
[INFO] Running com.metaobjects.registry.StandardServiceRegistryClassLoaderTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 s -- in com.metaobjects.registry.StandardServiceRegistryClassLoaderTest
[INFO] Running com.metaobjects.registry.StaticBlockDebugTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.033 s -- in com.metaobjects.registry.StaticBlockDebugTest
[INFO] Running com.metaobjects.registry.BasicRegistryTest
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s -- in com.metaobjects.registry.BasicRegistryTest
[INFO] Running com.metaobjects.registry.UnifiedRegistryTest
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in com.metaobjects.registry.UnifiedRegistryTest
[INFO] Running com.metaobjects.registry.InheritanceRobustnessTest
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.222 s -- in com.metaobjects.registry.InheritanceRobustnessTest
[INFO] Running com.metaobjects.registry.MetaDataModuleComplianceTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.250 s -- in com.metaobjects.registry.MetaDataModuleComplianceTest
[INFO] Running com.metaobjects.registry.ObjectClassRegistryTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 s -- in com.metaobjects.registry.ObjectClassRegistryTest
[INFO] Running com.metaobjects.registry.RegistryManifestConformanceTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.133 s -- in com.metaobjects.registry.RegistryManifestConformanceTest
[INFO] Running com.metaobjects.registry.StaticRegistrationTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.028 s -- in com.metaobjects.registry.StaticRegistrationTest
[INFO] Running com.metaobjects.registry.TypeDefinitionInheritanceTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in com.metaobjects.registry.TypeDefinitionInheritanceTest
[INFO] Running com.metaobjects.registry.InheritanceDebugTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s -- in com.metaobjects.registry.InheritanceDebugTest
[INFO] Running com.metaobjects.

... [48743117 bytes truncated] ...

attribute constraint [*.*.aliases.array] for attribute [aliases] with value [null] on [field:boolean:active]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.cache.HybridCache -- Cleared hybrid cache (all cache types)
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.MetaData -- Created MetaData: object:entity:verify::demo::Gadget
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.object.MetaObject -- Created MetaObject: object:entity:verify::demo::Gadget
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Enforcing 102 constraints for adding [EntityMetaObject[object:entity]{verify::demo::Gadget}] to [MetaRoot[metadata:root]{kotlin_spi_test}]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [seeAlso] not found in [object:entity:verify::demo::Gadget], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.seeAlso.array] for attribute [seeAlso] with value [null] on [object:entity:verify::demo::Gadget]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [aliases] not found in [object:entity:verify::demo::Gadget], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.aliases.array] for attribute [aliases] with value [null] on [object:entity:verify::demo::Gadget]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.cache.HybridCache -- Cleared hybrid cache (all cache types)
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.MetaData -- Created MetaData: field:long:id
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.field.MetaField -- Created MetaField: field:long:id with dataType: LONG
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Enforcing 102 constraints for adding [LongField[field:long]{id}{dataType=LONG, defaultValue=null}] to [EntityMetaObject[object:entity]{verify::demo::Gadget}@kotlin_spi_test]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [seeAlso] not found in [field:long:id], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.seeAlso.array] for attribute [seeAlso] with value [null] on [field:long:id]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [aliases] not found in [field:long:id], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.aliases.array] for attribute [aliases] with value [null] on [field:long:id]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.cache.HybridCache -- Cleared hybrid cache (all cache types)
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.MetaData -- Created MetaData: field:string:label
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.field.MetaField -- Created MetaField: field:string:label with dataType: STRING
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Enforcing 102 constraints for adding [StringField[field:string]{label}{dataType=STRING, defaultValue=null}] to [EntityMetaObject[object:entity]{verify::demo::Gadget}@kotlin_spi_test]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [seeAlso] not found in [field:string:label], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.seeAlso.array] for attribute [seeAlso] with value [null] on [field:string:label]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Attribute [aliases] not found in [field:string:label], returning null
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.constraint.ConstraintEnforcer -- Validating attribute constraint [*.*.aliases.array] for attribute [aliases] with value [null] on [field:string:label]
11:18:25.853 [ForkJoinPool.commonPool-worker-2] DEBUG com.metaobjects.cache.HybridCache -- Cleared hybrid cache (all cache types)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in com.metaobjects.mojo.KotlinGeneratorViaGenSpiTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 167, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- jacoco:0.8.13:report (report) @ metaobjects-maven-plugin ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jar:3.4.1:jar (default-jar) @ metaobjects-maven-plugin ---
[INFO] Building jar: ~/.no-mistakes/worktrees/5b8c6c97e760/01M4NK4YJV84WSN9JNKQPFG7W4/server/java/maven-plugin/target/metaobjects-maven-plugin-8.1.0.jar
[INFO] 
[INFO] --- plugin:3.15.1:addPluginArtifactMetadata (default-addPluginArtifactMetadata) @ metaobjects-maven-plugin ---
[INFO] This Mojo is not used in Maven version 3.9.0 and above
[INFO] 
[INFO] --- jacoco:0.8.13:prepare-agent-integration (prepare-agent-integration) @ metaobjects-maven-plugin ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:report-integration (report-integration) @ metaobjects-maven-plugin ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:report-aggregate (report-aggregate) @ metaobjects-maven-plugin ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- jacoco:0.8.13:check (coverage-check) @ metaobjects-maven-plugin ---
[INFO] Skipping JaCoCo execution because property jacoco.skip is set.
[INFO] 
[INFO] --- install:3.1.3:install (default-install) @ metaobjects-maven-plugin ---
[INFO] Installing ~/.no-mistakes/worktrees/5b8c6c97e760/01M4NK4YJV84WSN9JNKQPFG7W4/server/java/maven-plugin/pom.xml to ~/.m2-ci/repository/com/metaobjects/metaobjects-maven-plugin/8.1.0/metaobjects-maven-plugin-8.1.0.pom
[INFO] Installing ~/.no-mistakes/worktrees/5b8c6c97e760/01M4NK4YJV84WSN9JNKQPFG7W4/server/java/maven-plugin/target/metaobjects-maven-plugin-8.1.0.jar to ~/.m2-ci/repository/com/metaobjects/metaobjects-maven-plugin/8.1.0/metaobjects-maven-plugin-8.1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for MetaObjects Core 8.1.0:
[INFO] 
[INFO] MetaObjects Core ................................... SUCCESS [  0.682 s]
[INFO] MetaObjects :: MetaData ............................ SUCCESS [ 16.893 s]
[INFO] MetaObjects :: Render .............................. SUCCESS [  1.850 s]
[INFO] MetaObjects :: ObjectManager ....................... SUCCESS [  1.919 s]
[INFO] MetaObjects :: Code Generation :: Base ............. SUCCESS [  6.893 s]
[INFO] MetaObjects :: Code Generation :: Mustache ......... SUCCESS [  3.622 s]
[INFO] MetaObjects :: ObjectManager :: RDB ................ SUCCESS [ 12.032 s]
[INFO] MetaObjects :: Codegen :: Spring ................... SUCCESS [ 29.205 s]
[INFO] MetaObjects :: Metadata Kotlin Facade .............. SUCCESS [ 26.074 s]
[INFO] MetaObjects :: Codegen :: Kotlin ................... SUCCESS [02:42 min]
[INFO] MetaObjects :: Code Generation :: PlantUML ......... SUCCESS [  2.274 s]
[INFO] MetaObjects :: Maven Plugin ........................ SUCCESS [ 34.792 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:59 min
[INFO] Finished at: 2026-10-11T11:18:26-04:00
[INFO] ------------------------------------------------------------------------
EXIT=0
Evidence: generate on packaged entity+extractor
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< live.shop:live-shop >-------------------------
[INFO] Building live-shop 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- metaobjects:8.1.0:generate (default-cli) @ live-shop ---
[INFO] OM managed types registered via provider
[INFO] MetaData Mojo > LifeCycle Phase: null
[INFO] MetaData Mojo > Create Loader: MetaDataLoader[manual:null]
[INFO] MetaData Mojo > Executing Generator: com.metaobjects.generator.direct.object.javacode.JavaObjectCodeGenerator
[INFO] Writing Java Code [class] to file: Product.java
[INFO] Writing Java Code [class] to file: Order.java
[INFO] MetaData Mojo > Executing Generator: com.metaobjects.generator.direct.object.javacode.ExtractorCodeGenerator
[INFO] MetaData Mojo > Registered compile source root: /tmp/mo-live/target/generated-sources/java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.055 s
[INFO] Finished at: 2026-10-11T11:19:00-04:00
[INFO] ------------------------------------------------------------------------
Evidence: verify on unchanged model
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< live.shop:live-shop >-------------------------
[INFO] Building live-shop 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- metaobjects:8.1.0:verify (default-cli) @ live-shop ---
[INFO] OM managed types registered via provider
[INFO] MetaData Mojo > LifeCycle Phase: null
[INFO] MetaData Mojo > Create Loader: MetaDataLoader[manual:null]
[INFO] MetaData Verify Mojo > Regenerating (temp): com.metaobjects.generator.direct.object.javacode.JavaObjectCodeGenerator
[INFO] Writing Java Code [class] to file: Product.java
[INFO] Writing Java Code [class] to file: Order.java
[INFO] MetaData Verify Mojo > Regenerating (temp): com.metaobjects.generator.direct.object.javacode.ExtractorCodeGenerator
[INFO] MetaData Verify Mojo > No codegen drift detected across 2 generator(s) in 1 output director(y/ies).
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.452 s
[INFO] Finished at: 2026-10-11T11:19:12-04:00
[INFO] ------------------------------------------------------------------------
Evidence: verify fails on tampered generated file
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< live.shop:live-shop >-------------------------
[INFO] Building live-shop 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- metaobjects:8.1.0:verify (default-cli) @ live-shop ---
[INFO] OM managed types registered via provider
[INFO] MetaData Mojo > LifeCycle Phase: null
[INFO] MetaData Mojo > Create Loader: MetaDataLoader[manual:null]
[INFO] MetaData Verify Mojo > Regenerating (temp): com.metaobjects.generator.direct.object.javacode.JavaObjectCodeGenerator
[INFO] Writing Java Code [class] to file: Product.java
[INFO] Writing Java Code [class] to file: Order.java
[INFO] MetaData Verify Mojo > Regenerating (temp): com.metaobjects.generator.direct.object.javacode.ExtractorCodeGenerator
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.304 s
[INFO] Finished at: 2026-10-11T11:19:18-04:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.metaobjects:metaobjects-maven-plugin:8.1.0:verify (default-cli) on project live-shop: generated code is stale — run `mvn metaobjects:generate` and commit. Drifted files:
[ERROR]   [content-differs] /tmp/mo-live/target/generated-sources/java/live/shop/Product.java
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
Evidence: eject entity and extractor
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< live.shop:live-shop >-------------------------
[INFO] Building live-shop 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- metaobjects:8.1.0:eject (default-cli) @ live-shop ---
[INFO] Ejected "entity" -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/JavaObjectCodeGenerator.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected JavaCodeGenerator (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/JavaCodeGenerator.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected BaseObjectCodeGenerator (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/BaseObjectCodeGenerator.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected BaseObjectCodeWriter (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/BaseObjectCodeWriter.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected JavaCodeWriter (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/JavaCodeWriter.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected PojoAwareCodeWriter (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/PojoAwareCodeWriter.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected ValueObjectCodeWriter (with "entity") -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/ValueObjectCodeWriter.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Ejected "extractor" -> /tmp/mo-owned/codegen/src/main/java/live/shop/codegen/ExtractorCodeGenerator.java. You own it now (ADR-0034 scaffold-and-own).
[INFO] Wrote /tmp/mo-owned/codegen/pom.xml
[INFO] 
[INFO] Add this module to the parent pom's <modules>:
[INFO]   <module>codegen</module>
[INFO] Add this dependency to the app module's metaobjects-maven-plugin <plugin>:
[INFO]   <dependency>
[INFO]     <groupId>live.shop</groupId>
[INFO]     <artifactId>live-shop-codegen</artifactId>
[INFO]     <version>1.0.0</version>
[INFO]   </dependency>
[INFO] Change each ejected generator's <classname> in <generators>:
[INFO]   entity: com.metaobjects.generator.direct.object.javacode.JavaObjectCodeGenerator  ->  live.shop.codegen.JavaObjectCodeGenerator
[INFO]   extractor: com.metaobjects.generator.direct.object.javacode.ExtractorCodeGenerator  ->  live.shop.codegen.ExtractorCodeGenerator
[INFO] extractor: `entity` writes the <Name>Extractor classes itself unless a separate extractor generator is in <generators>. If yours has no entry for one, add <generator><classname>live.shop.codegen.ExtractorCodeGenerator</classname></generator> with the same outputDir, pkgPrefix, pkgSuffix, namePrefix and nameSuffix args, and the same <filters>, as your entity generator: an extractor for an object entity wrote no class for does not compile. `entity` then leaves those files to it. A pom with more than one entity entry needs one extractor entry per entity entry, each with that entry's outputDir, naming args and <filters>: a single extractor generator in the run stops every entity entry writing its extractors.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.841 s
[INFO] Finished at: 2026-10-11T11:19:24-04:00
[INFO] ------------------------------------------------------------------------
Evidence: generate with owned writer edit
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< live.shop:live-shop >-------------------------
[INFO] Building live-shop 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- metaobjects:8.1.0:generate (default-cli) @ live-shop ---
[INFO] OM managed types registered via provider
[INFO] MetaData Mojo > LifeCycle Phase: null
[INFO] MetaData Mojo > Create Loader: MetaDataLoader[manual:null]
[INFO] MetaData Mojo > Executing Generator: live.shop.codegen.JavaObjectCodeGenerator
[INFO] Writing Java Code [class] to file: Product.java
[INFO] Writing Java Code [class] to file: Order.java
[INFO] MetaData Mojo > Executing Generator: live.shop.codegen.ExtractorCodeGenerator
[INFO] MetaData Mojo > Registered compile source root: /tmp/mo-owned/target/generated-sources/java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.297 s
[INFO] Finished at: 2026-10-11T11:20:01-04:00
[INFO] ------------------------------------------------------------------------

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • ⚠️ spec/decisions/ADR-0034-codegen-scaffold-and-own.md:371 - Intent: "Yes, everything should be ejectable" / "Go with recommended extraction, everything except template", with template excluded only because it has "no emit logic to own", plus "Don't ever do workarounds just to get a release out". The change makes Java entity ejectable as the 271-line JavaObjectCodeGenerator.java only. The class-body emit logic (about 930 lines: BaseObjectCodeWriter, JavaCodeWriter, PojoAwareCodeWriter, ValueObjectCodeWriter) is explicitly not ejectable: ADR-0034 Amendment 6 Decision 1 ("the class body stays in the package"), and server/java/codegen-base/pom.xml:103 ("(JavaCodeWriter, PojoAwareCodeWriter, ValueObjectCodeWriter) are NOT shipped"). An adopter who ejects entity owns flavor selection, the binding provider and the services file, but can change a field, constructor or accessor only by subclassing a packaged writer. Every other ejected Java generator carries its own emit (for example SpringDtoGenerator, 1400 lines). Decision 1 is the author's, not part of the quoted maintainer ruling. Its first stated reason (a copied writer changes the ObjectCodeWriter: header line in every generated file) applies equally to the recommended subclass path, since BaseObjectCodeWriter.java:221 prints getClass().getName(). Needs a maintainer ruling: is a generator-shell eject with the writers as supported surface acceptable for 1.1, or must the writers eject too?
  • ⚠️ server/java/maven-plugin/src/test/java/com/metaobjects/mojo/EntityExtractorEjectRoundTripTest.java:430 - generatedInSync retries generate+verify up to 5 times and treats drift as acceptable when the wall clock crossed a second. Cause: every entity class header carries Generated On: new Date() at second resolution (BaseObjectCodeWriter.java:222, unchanged), and MetaDataVerifyMojo.compareTrees compares raw bytes (MetaDataVerifyMojo.java:421). Two consequences. (1) The test is timing dependent: it fails whenever all 5 attempts span a second boundary, which is certain on a runner where generate+verify of the fitness corpus takes 1 s or more. (2) The retry hides that mvn metaobjects:verify reports [content-differs] for every class file of any pom that wires entity (packaged or owned) unless verify runs in the same second as generate. The CHANGELOG entry ("mvn metaobjects:verify runs them when your pom names them") and the ADR-0034 Amendment 6 "Proof" paragraph present verify as working for the owned entity. The same clock is stripped in the comparisons at line 73 and in ReportingInertTest. The defect is pre-existing, but this change adds a test that works around it and a doc claim that depends on it, under the instruction "Don't ever do workarounds just to get a release out". The smallest honest remedy is to remove or normalise the clock line in the writer header (or in verify's compare). That changes packaged output for every adopter, so the remedy, not the defect, needs authorization.
  • ⚠️ server/java/codegen-base/src/main/java/com/metaobjects/generator/EmitsObjectExtractors.java:46 - Simplification: the args.containsKey(ARG_EMIT_EXTRACTORS) branch ("an explicit value in the pom always wins") is not required by the intent. The arg must exist for the plugin to pass the derived value and for non-Maven callers, who set it directly and never reach this method. The explicit-wins precedence adds one reachable outcome in Maven: &lt;emitExtractors&gt;true&lt;/emitExtractors&gt; on entity (or in global args) beside a separate extractor restores two emitters of each &lt;Name&gt;Extractor, which is the exact state the marker was added to prevent (identical bytes hide it; the first edit to the owned extractor fails as Output path collision). No use case for that combination is stated. Recommend removing the precedence branch so the derivation always sets false when an EmitsObjectExtractors generator is in the run. Sibling sites that assert it: EmitExtractorsDerivationTest.java:76 (an_explicit_pom_value_wins_over_the_derivation), StandaloneExtractorGeneratorTest.java:215, and the ADR/CHANGELOG sentences.
  • ⚠️ server/java/codegen-base/src/main/java/com/metaobjects/generator/direct/object/javacode/JavaObjectCodeGenerator.java:79 - Simplification: JavaObjectCodeGenerator.ARG_EMIT_EXTRACTORS is a second public name for EmitsObjectExtractors.ARG_EMIT_EXTRACTORS, and the CHANGELOG lists it as new public API. No intent requirement needs two names for one arg, and the copy becomes supported surface that an ejected entity and adopters can bind to. Recommend removing the alias and reading EmitsObjectExtractors.ARG_EMIT_EXTRACTORS directly (the file already imports the interface); update the two test call sites and the CHANGELOG bullet.

🔧 Fix applied.
3 issues (2 warnings, 1 info) still open:

  • ⚠️ CHANGELOG.md:52 - The round 1 decision says: "CHANGELOG [1.1.0] and docs/ports/java.md: state the one-time header change in every regenerated Java class, and that verify is now clock-independent." The intent also says "ejectable in 1.1.0". The round 1 fix put every entry for this change under ## [Unreleased] (CHANGELOG.md:11): the Added block at line 15, the header change at line 52 and the -Dport change at line 63. ## [1.1.0] — 2026-10-10 (line 82) is untouched. The fixer followed what main does after chore(release): prepare 1.1.0 (chore(release): prepare 1.1.0 (npm/PyPI/NuGet 1.1.0, Maven 8.1.0) #417), where later entries (fix(verify,cli): verify --docs catches orphan model pages; hand-rolled-aggregate advice names object.report #420) also sit under [Unreleased]. The source cannot show whether 1.1.0 is still open. Decide: move the three entries into [1.1.0] (if this ships in 1.1.0), or confirm [Unreleased] (if 1.1.0 is already published). docs/ports/java.md:528 states the header change without a version, so it is correct either way.
  • ⚠️ server/java/codegen-base/src/main/java/com/metaobjects/generator/direct/object/javacode/JavaCodeWriter.java:233 - Simplification: JavaCodeWriter.javaPackageOf(mo) and javaClassNameOf(mo) are new public API that no intent requirement needs any more. The original commit added them because a lone ejected JavaObjectCodeGenerator had to call protected naming steps on a packaged writer in another package. The round 1 fix copies JavaCodeWriter with both generators, so each caller is always in the same package as its JavaCodeWriter, packaged or owned, and protected access compiles there (the base commit called namer.getLanguagePackage(mo) / namer.getClassName(mo) directly from that package). Round 1 left the views and their justification behind. Recommend removing both methods and calling the protected steps directly. Sites to change in the same pass: JavaObjectCodeGenerator.java:162-163, ExtractorCodeGenerator.java:101, CHANGELOG.md:43 ("New public API" bullet), ADR-0034 Amendment 6 Decision 2 (spec/decisions/ADR-0034-codegen-scaffold-and-own.md:403), README-flavored-objects.md:113 (deviation 6). This removes a documented public API, so the author decides.
  • ℹ️ server/java/codegen-spring/src/main/java/com/metaobjects/generator/GeneratorRegistry.java:143 - Three new overloads only forward to the next one and have no other caller: the public GeneratorInfo(..., ejectRuntime, ejectNote) constructor (GeneratorRegistry.java:141-146), the private register(..., ejectRuntime, ejectNote) (GeneratorRegistry.java:366-372), and the public EjectSupport.Entry(..., runtime, note) constructor (EjectSupport.java:81-84). The only real call sites pass the full argument list (GeneratorRegistry.java:378, EjectSupport.java:147) or the pre-existing shorter forms. Remove the three and chain the pre-existing overloads straight to the full one.

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ⚠️ server/java/codegen-base/src/main/java/com/metaobjects/generator/direct/object/javacode/JavaCodeWriter.java:233 - Simplification: JavaCodeWriter.javaPackageOf(mo) and javaClassNameOf(mo) are new public API that no intent requirement needs any more. The original commit added them because a lone ejected JavaObjectCodeGenerator had to call protected naming steps on a packaged writer in another package. The round 1 fix copies JavaCodeWriter with both generators, so each caller is always in the same package as its JavaCodeWriter, packaged or owned, and protected access compiles there (the base commit called namer.getLanguagePackage(mo) / namer.getClassName(mo) directly from that package). Round 1 left the views and their justification behind. Recommend removing both methods and calling the protected steps directly. Sites to change in the same pass: JavaObjectCodeGenerator.java:162-163, ExtractorCodeGenerator.java:101, CHANGELOG.md:43 ("New public API" bullet), ADR-0034 Amendment 6 Decision 2 (spec/decisions/ADR-0034-codegen-scaffold-and-own.md:403), README-flavored-objects.md:113 (deviation 6). This removes a documented public API, so the author decides.
  • ℹ️ server/java/codegen-base/src/main/java/com/metaobjects/generator/EmitsObjectExtractors.java:47 - Tradeoff of the round 1 decision to remove the explicit-wins branch, recorded for awareness only. deriveEmitExtractors sets emitExtractors=false on every generator in the run when any one EmitsObjectExtractors generator is present. Trace: a pom with two entity entries (entity A: filters pkgA, outputDir X; entity B: filters pkgB, outputDir Y) plus one extractor entry given entity A's args and filters. AbstractMetaDataMojo.buildGenerators (line 208-213) tells both entity generators to stop, so the &lt;Name&gt;Extractor files for pkgB are no longer written and nothing reports it at generate time. The supported remedy already exists: add one extractor entry per entity entry. The docs say "the same args as your entity generator" in the singular (docs/ports/java.md:297-305, docs/features/own-your-codegen.md:581-597, the eject note in GeneratorRegistry.java:295-300) and do not state the one-per-entity rule. No code change is recommended; a pom with a single entity entry is not affected.

🔧 Fix applied.
1 warning still open:

  • ⚠️ server/java/codegen-base/src/main/java/com/metaobjects/generator/direct/object/javacode/JavaCodeWriter.java:233 - Simplification: JavaCodeWriter.javaPackageOf(mo) and javaClassNameOf(mo) are new public API that no intent requirement needs any more. The original commit added them because a lone ejected JavaObjectCodeGenerator had to call protected naming steps on a packaged writer in another package. The round 1 fix copies JavaCodeWriter with both generators, so each caller is always in the same package as its JavaCodeWriter, packaged or owned, and protected access compiles there (the base commit called namer.getLanguagePackage(mo) / namer.getClassName(mo) directly from that package). Round 1 left the views and their justification behind. Recommend removing both methods and calling the protected steps directly. Sites to change in the same pass: JavaObjectCodeGenerator.java:162-163, ExtractorCodeGenerator.java:101, CHANGELOG.md:43 ("New public API" bullet), ADR-0034 Amendment 6 Decision 2 (spec/decisions/ADR-0034-codegen-scaffold-and-own.md:403), README-flavored-objects.md:113 (deviation 6). This removes a documented public API, so the author decides.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Packaged generate with entity and extractor entries writes exactly one <Name>Extractor per entity (no duplicate emitter) ✅ pass live 01-generate-packaged.log; target/generated-sources/java/live/shop/{Product,Order}Extractor.java
Generated output is byte-identical across a second wall-clock boundary and has no 'Generated On' timestamp line ✅ pass live diff -r of two generate runs 2s apart returned no differences; grep 'Generated On' count 0
mvn metaobjects:verify passes on an unchanged model ✅ pass live 02-verify-unchanged.log: 'No codegen drift detected across 2 generator(s)'
Adversarial: hand-edited generated file is caught by verify ✅ pass live 03-verify-tampered-fails.log: BUILD FAILURE, [content-differs] Product.java
mvn metaobjects:eject -Dnames=entity,extractor copies the generator and every writer it emits through ✅ pass live 04-eject-entity-extractor.log: 7 owned files ejected including BaseObjectCodeWriter, JavaCodeWriter, PojoAwareCodeWriter, ValueObjectCodeWriter
Owned generator output is byte-identical to packaged generator output ✅ pass live diff -r of owned-pom generate vs packaged generate: identical (6 files)
Editing an owned writer changes the generated class body ✅ pass live 05-generate-owned-with-writer-edit.log; marker OWNED-EDIT-MARKER present in Product.java and Order.java after rebuild and regenerate
Generated code compiles with packaged and owned generators ✅ pass live mvn -o compile exit 0 on both disposable projects
Explicit emitExtractors=true on entity alongside an extractor entry still yields the same output ✅ pass live generate exit 0 and diff -r against baseline output identical. Limitation: both paths write the same file path, so a duplicate write would be invisible here; the derivation itself is covered by the Ja…
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • cd server/java && mvn -Dmaven.repo.local=$HOME/.m2-ci/repository -pl codegen-base,codegen-spring,maven-plugin -am install -Djacoco.skip=true (BUILD SUCCESS, exit 0)
  • cd server/typescript && bun test packages/sdk (329 pass, 0 fail; agent-context goldens)
  • Disposable Maven project in /tmp with entity+extractor generators: mvn -o metaobjects:generate (packaged, 8.1.0)
  • Re-generate after 2s sleep, diff -r against first output (byte-identical); grep for 'Generated On' header (none)
  • mvn -o metaobjects:verify on unchanged model (clean); after tampering Product.java (drift reported, BUILD FAILURE)
  • mvn -o metaobjects:eject -Dnames=entity,extractor -Dport=java (7 owned files: JavaObjectCodeGenerator, JavaCodeGenerator, BaseObjectCodeGenerator, BaseObjectCodeWriter, JavaCodeWriter, PojoAwareCodeWriter, ValueObjectCodeWriter, ExtractorCodeGenerator)
  • Owned codegen module mvn -o install, pom rewired to owned classnames + plugin dependency; generate output diff -r against packaged output (identical)
  • Added writeComment marker in owned PojoAwareCodeWriter, rebuilt, regenerated: marker present in Product.java and Order.java
  • mvn -o compile on packaged and owned projects (both exit 0)
  • Packaged pom with <emitExtractors>true</emitExtractors> on entity plus extractor entry: generate exit 0, output identical to baseline
  • Evidence logs in ~/.no-mistakes/evidence/01M4NK4YJV84WSN9JNKQPFG7W4 (00-java-module-build-tests.log, 01-generate-packaged.log, 02-verify-unchanged.log, 03-verify-tampered-fails.log, 04-eject-entity-extractor.log, 05-generate-owned-with-writer-edit.log)
  • Regression: full Java reactor scope above; TS lanes from baseline (ts-fast, ts-unit) already green. Not run: full scripts/ci-local.sh (C#/Kotlin/Python lanes untouched by this diff).
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → no changes applied ✅
  • ⚠️ linter found issues (exit code 1)

🔧 No changes applied.
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

"Everything should be ejectable." An audit of the stock generators in all five
ports found eight that could not be ejected; this is the Java part, `entity`
(JavaObjectCodeGenerator) and `extractor` (ExtractorCodeGenerator). Every Java
generator can now be ejected except `template` (ADR-0034 Amendment 6).

Why they could not be ejected
- entity compiled only inside its writers' package: it called two protected
  naming steps on a writer it does not extend, and named its siblings by bare
  name. The package line is the one thing eject rewrites, so a copy did not
  compile (39 errors).
- extractor was not a generator. It was a step entity ran, so no <generator>
  entry could name an owned copy and the drift check had nothing to run.

What the owned copy carries, and what stays in the package
- The owned entity is JavaObjectCodeGenerator.java alone: flavor selection, the
  writer factory (createWriter), the ObjectClassBindingProvider source, its
  META-INF/services registration, and whether the extractors are written there.
- The class body stays in codegen-base's flavor writers (JavaCodeWriter,
  PojoAwareCodeWriter, ValueObjectCodeWriter), now named as supported surface:
  subclass one, override a protected step, return it from createWriter. They
  are not copied because every generated class's header names the writer class
  that wrote it, so a copied writer would change every generated file, and
  because a change to the class body is one overridden step.
- JavaCodeWriter gains javaPackageOf(mo) and javaClassNameOf(mo), public views
  of the naming steps. The steps stay protected: widening them would break
  every subclass that overrides one as protected.
- The owned extractor holds all of its emit.

extractor as a generator of its own
- ExtractorCodeGenerator implements Generator and takes the naming args entity
  takes. entity still writes the extractors when no separate extractor is in
  the run, so a pom that wires only entity emits byte for byte what it did.
- When one is in <generators>, the Maven plugin sets emitExtractors=false on
  the others (EmitsObjectExtractors, derived where useNames is), so each file
  has one emitter. An explicit value in the pom wins.

Eject
- codegen-base ships the two reference sources. The registry gives both an
  eject path, and extractor a wiring note the eject report prints, since a pom
  may have no <generator> entry for it to change.
- Behaviour change: `-Dnames=entity` and `-Dnames=extractor` are now ejectable
  on both JVM ports, so with no port implied by the project's dependencies the
  goal asks for -Dport, as it does for `names`. It used to resolve to Kotlin.

Proof
- EjectedBaseGeneratorsCompileTest: both copies compile package-renamed against
  the module's public API.
- EntityExtractorEjectRoundTripTest: ejects both through the mojo, compiles
  them, and holds the unchanged copies to the packaged generators' output over
  the persistence corpus in every flavor; an edit to each copy shows in its
  output; -Dlist marks the copies identical or DIFFERS; mvn metaobjects:verify
  runs the owned copies and reports what they no longer produce.
- StandaloneExtractorGeneratorTest, EmitExtractorsDerivationTest and a second
  compile-gate selection hold the split between the two generators.

The model tier's class header still carries a wall-clock "Generated On:" line,
so the entity comparisons leave that one line out and the drift proof re-runs
a pass that crossed a second. Both go once that line is removed.

Docs: ADR-0034 Amendment 6, own-your-codegen, the Java port page and README
matrix, the codegen skill's Java reference (with regenerated agent-context
goldens), the module README and the CHANGELOG.
…d test it

Pre-gate review found that nothing told an adopter the separate extractor
generator has to select the same objects as entity. Each <generator> merges its
own <filters>, so an extractor wired without entity's filters writes a
<Name>Extractor for an object entity wrote no class for, which does not compile.

- The eject wiring note, ExtractorCodeGenerator's javadoc, own-your-codegen, the
  Java port page, the codegen skill's Java reference (goldens regenerated) and
  the CHANGELOG now name <filters> beside the naming args.
- StandaloneExtractorGeneratorTest holds that the two generators, given the same
  filters, select the same objects.
- EjectRuntimeRoundTripTest runs every ejectable Java generator off the registry;
  entity is one now, so its shared args carry entity's required type and flavor.
  That test also holds that neither generator's output imports a codegen helper,
  so ejecting them copies no runtime.
…hange

- The eject mojo reads each wiring note from the resolved entries when it
  prints, instead of collecting them in a second list during the copy loop.
- EjectedBaseGeneratorsCompileTest compiles through JavaCompileTestSupport, the
  module's shared compile helper, which also dumps the sources on failure.
- EmitExtractorsDerivationTest reuses UseNamesDerivationTest's recording
  generator instead of carrying a copy of it.
- A literal "false" and an imported StringWriter; no output changes.
…gle extractor emitter

Assisted-by: no-mistakes:claude:claude-opus-5-5
…verloads

Assisted-by: no-mistakes:claude:claude-opus-5-5
Assisted-by: no-mistakes:claude:claude-opus-5-5
@dmealing
dmealing merged commit f3a2fa7 into main Oct 11, 2026
1 check passed
@dmealing
dmealing deleted the fm/mo-1-1-0-ejectable-java branch October 11, 2026 15:27
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