Skip to content

Commit cfb2d2c

Browse files
committed
docs: the jsonb ladder can now recommend a value object to a Java consumer
The ladder tells an author to convert an untyped jsonb bag to an `object.value`, and until bc591b7 that advice was wrong for anyone whose Java code CONSTRUCTS the result: the generated data class offered Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an immutable class. The ladder never said so, because nothing had tried it. Adds that to the "Port coverage, stated plainly" section as a VERSION question, matching how the #362 caveat is framed two paragraphs up — the same shape of fact, and the same thing an author needs to check before recommending a rung. It is explicitly marked UNRELEASED, because it is: the builder is on `main` and ships in the next JVM cut. Reading was never affected on any version (Kotlin `val` emits Java getters), so a bag that is only READ converts safely today. A doc that hedges on an unreleased capability becomes a lie the moment the cut lands, which is the [[version-forecast]] trap in miniature — so `docs/RELEASING-docs-checklist.md` §A grows an item to grep for that hedge and flip it, regenerating the agent-context corpus in the same commit. CHANGELOG [Unreleased] carries the entry, including why a builder rather than `@JvmOverloads`, why not Lombok, why `object.projection` gets none, and that `metamodelVersion` does not move. Corpus regenerated (the authoring skill lands in all five stacks). Gates green.
1 parent bc591b7 commit cfb2d2c

8 files changed

Lines changed: 102 additions & 0 deletions

File tree

  • agent-context/skills/metaobjects-authoring
  • docs
  • fixtures/agent-context-conformance
    • java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring
    • java-react/expected/.claude/skills/metaobjects-authoring
    • python/expected/.claude/skills/metaobjects-authoring
    • ts-react-tanstack/expected/.claude/skills/metaobjects-authoring
    • ts-requirements/expected/.claude/skills/metaobjects-authoring

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ here.**
1010

1111
## [Unreleased]
1212

13+
### Added
14+
15+
- **A generated Kotlin entity / value object is now constructible from Java** — a nested
16+
`Builder` plus a `@JvmStatic builder()` ([#365]). Kotlin default arguments are a compiler
17+
feature, not a bytecode one, so the generated data class previously offered Java only the full
18+
N-arg constructor and a no-arg one yielding an all-null instance of an immutable class: a
19+
caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. Found when an adopter
20+
converting an untyped jsonb bag to an `object.value` discovered the generated VO replacing a
21+
hand-written Lombok `@Builder` made its Java call sites strictly worse. A builder rather than
22+
`@JvmOverloads` because overloads are positional and only help trailing omissions; a plain
23+
nested class rather than Lombok because generated code must not force an annotation processor
24+
on an adopter. `object.projection` gets none — it is derived and read-only. Kotlin callers are
25+
unaffected (named arguments already covered it), which is why every test in that module, all
26+
Kotlin-side, missed this; the new test compiles real Java against the generated class.
27+
`codegen-spring`'s Java `record`s have the same all-args shape and are tracked in #365 as a
28+
follow-on. No vocabulary change, so `metamodelVersion` does not move.
29+
30+
1331
## [1.0.1] — 2026-09-10
1432

1533
**Why this is a PATCH and not a MINOR.** `docs/RELEASING.md`'s table says an additive CLI
@@ -103,6 +121,7 @@ adopter who read "field.map now works" out of this entry would be over-reading i
103121
`metamodelVersion` is unchanged at **1.0** — no registered vocabulary moved.
104122

105123
[#362]: https://github.com/metaobjectsdev/metaobjects/issues/362
124+
[#365]: https://github.com/metaobjectsdev/metaobjects/issues/365
106125
[#361]: https://github.com/metaobjectsdev/metaobjects/issues/361
107126
[#360]: https://github.com/metaobjectsdev/metaobjects/pull/360
108127
[#359]: https://github.com/metaobjectsdev/metaobjects/issues/359

agent-context/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

docs/RELEASING-docs-checklist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ by exactly one release.
3636
scope, so there is no "as of `<ver>`" marker left to refresh (checked at the 1.0 cut).
3737
Re-add one here only if parity ever becomes version-scoped again.
3838
- [ ] `docs/RELEASING.md` / `docs/RELEASING-java.md` — the "currently `<ver>`" notes.
39+
- [ ] **Grep the docs for "UNRELEASED" / "on `main`, shipping in the next".** A capability
40+
documented before it ships carries that hedge on purpose, and it becomes a lie the moment
41+
the cut lands. `agent-context/skills/metaobjects-authoring/SKILL.md` carries one today for
42+
the Kotlin builder (#365) — flip it to the shipped version and regenerate
43+
`fixtures/agent-context-conformance/` in the same commit.
3944
- [ ] `.github/workflows/publish-*.yml`**no version edits by design.** All four
4045
publish workflows read the version from the committed manifest
4146
(`package.json` / pom / `Directory.Build.props` / `pyproject.toml`), so none

fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

fixtures/agent-context-conformance/java-react/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

fixtures/agent-context-conformance/python/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

fixtures/agent-context-conformance/ts-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

fixtures/agent-context-conformance/ts-requirements/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,19 @@ Python's `ObjectManager` encodes a map today; `runtime-ts`, Java's OMDB and the
585585
lane carry no map handling at all. So a map you intend to read back through a PORT RUNTIME is
586586
still better declared as a value object, and a genuinely dynamic key set stays a bag.
587587

588+
**One more version question, if a JAVA caller CONSTRUCTS the value object.** Kotlin's default
589+
arguments are a compiler feature, not a bytecode one, so a generated data class used to offer
590+
Java only the full N-arg constructor and a no-arg one yielding an all-null instance of an
591+
immutable class — a caller setting 3 of 14 members had to pass 14 arguments with 11 nulls. A
592+
generated VO replacing a hand-written builder therefore made its Java call sites *worse*.
593+
Generated entities and value objects now emit a nested `Builder` plus a `@JvmStatic builder()`
594+
(`Money.builder().currency("USD").build()`), so partial construction from Java works — but that
595+
is **UNRELEASED at the time of writing: it is on `main`, shipping in the next JVM cut.** Check
596+
the adopter's pinned version. Reading is unaffected on every version (Kotlin `val` emits Java
597+
getters), so a bag that is only READ converts safely today; it is construction that was blocked.
598+
`object.projection` deliberately gets no builder — it is derived and read-only, and nothing
599+
constructs one.
600+
588601
**One sharp edge where generated code IS the consumer, and it is now a VERSION question rather
589602
than a port question: nested map values.** As of **1.0.1** every port validates them on every
590603
write path ([#362](https://github.com/metaobjectsdev/metaobjects/issues/362)). On **1.0.0 the

0 commit comments

Comments
 (0)