Skip to content

Commit b3eb355

Browse files
dmealingclaude
andcommitted
chore(release): 0.23.1 coordinated (npm/PyPI/NuGet 0.23.1 · Maven 7.23.1)
Every registry carries a real changed product file, so none of the four is a version-parity bump: npm (cli/codegen-ts/render), PyPI (render + fr019 generator), NuGet (MetaObjects.Render + Fr019SharedEnum), Maven (render). The theme is a check that was confidently wrong. Each fix is a guarantee the toolchain already made and quietly failed to keep, and each survived because the thing that would have caught it did not exist -- the render corpus had no fixture using a derived accessor at all, nothing compared codegen's CHECK-constraint name to migrate's, and no gate read the committed schema snapshot. CHANGELOG: adds the @provided entry the branch shipped without. That fix is adopter-visible in three registries -- a chained abstract enum declaration (B extends @provided A) was classified provided under a resolving read, so TS, C# and Python emitted a reference to a hand-written type the adopter never declared instead of materialising B from its inherited @values. Also corrects CLAUDE.md's "Current release line", which still read 0.21.x / 7.21.x after drifting through three cuts. Preflight evidence: - PR #296 merged; local-ci success on the release SHA dfdb60f - RELEASING.md Step 0 scan: 4 unmerged branches, each decided. feat/int-backed-enum-values waits -- it adds @intValueMap, registered vocabulary, so it is a MINOR and must not auto-adopt on a patch. The other three verified as already-shipped work on main. - check-publish-intent: OK (lockstep 0.23.0; 2 declared source-only) - 33 version files, each a clean 1/1 diff; packed cli tarball resolves workspace:* to 0.23.1 with no stale pins - clean rebuild + workspace typecheck green Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent dfdb60f commit b3eb355

43 files changed

Lines changed: 160 additions & 80 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
## [Unreleased]
99

10+
## [0.23.1] — npm `0.23.1` · PyPI `0.23.1` · NuGet `0.23.1` · Maven `7.23.1`
11+
12+
A coordinated **PATCH** across all four registries. Every one of them carries a real changed
13+
product file, so none is a version-parity bump.
14+
15+
**The theme is a check that was confidently wrong.** Not one of these fixes is a missing feature;
16+
each is a guarantee the toolchain already made and quietly failed to keep. A render pillar that
17+
promises byte-identical output across five ports dropped a conditional block's contents on four of
18+
them, silently, with no error. A `verify` gate told a correct project its tests did not exist,
19+
because it hardcoded another ecosystem's naming convention. A committed schema snapshot decided
20+
what DDL the next migration contained and nothing checked it, so `verify` reported healthy while
21+
`migrate` emitted DDL that failed at apply. Codegen and migrate named the same CHECK constraint
22+
two different ways, each internally consistent and separately tested. And `@provided` — a marker
23+
meaning "this type is hand-written, emit nothing" — was inherited down an `extends` chain in three
24+
ports, so those ports emitted a reference to a type the adopter never declared.
25+
26+
The recurring shape is worth naming: **each survived because the thing that would have caught it
27+
did not exist.** The render conformance corpus had no fixture using a derived accessor at all;
28+
nothing ever compared codegen's constraint name to migrate's; no gate read the snapshot. Every fix
29+
below ships with the missing check, not just the corrected behaviour.
30+
1031
### Fixed — `{{#hasField}}` rendered as absent on a populated payload, in every port (npm/PyPI/NuGet/Maven)
1132

1233
A prompt's conditional section — *"include the abilities block only when there ARE
@@ -45,6 +66,43 @@ both. Gated by the shared `render-derived-has-accessor` conformance case — **t
4566
no fixture using a derived accessor at all**, which is precisely why a divergence in the
4667
pillar that promises byte-identical rendering survived this long.
4768

69+
### Fixed — `@provided` flowed down an `extends` chain in TypeScript, C# and Python (npm/PyPI/NuGet)
70+
71+
`@provided` marks a shared enum declaration as supplied by hand-written or third-party code
72+
([ADR-0026](spec/decisions/ADR-0026-shared-and-provided-named-types.md)): the port emits nothing and references
73+
the existing type. **TS, C# and Python read it RESOLVING; Java and Kotlin read it own-only and
74+
documented that as deliberate.** One of them had to be wrong.
75+
76+
The JVM side is right. `@provided` is a provenance fact about the declaration *itself* — like
77+
`abstract` — not a property of the values it carries, so it must not be inherited. All five
78+
ports already read it on the resolved *declaration* and never on the consuming field, so for
79+
the ordinary `field extends @provided decl` shape own and resolving agree. The divergence is
80+
reachable only through a **chained** declaration — a root-level abstract enum `B extends` a
81+
root-level abstract `@provided A`. Verified against the real loader: that model loads clean,
82+
`B`'s own `@provided` is absent while its resolving read is `true`. So the resolving ports
83+
classified `B` as provided and **emitted a reference to a hand-written `B` the adopter never
84+
declared** (the marker was authored on `A`), instead of materialising `B` from its inherited
85+
`@values`.
86+
87+
Neither resolving port held a reasoned position: Python's docstring justified it with "a
88+
concrete enum extending an abstract `@provided` enum inherits the flag, so an own-only read
89+
would misclassify it" — wrong about its own call graph, since `is_provided()` is only ever
90+
passed the declaration — and C#'s comment simply cited TypeScript.
91+
92+
**Blast radius is nil on existing gated output:** every currently-pinned model shape yields the
93+
same answer under both reads, which is exactly why this survived. [ADR-0039](spec/decisions/ADR-0039-own-accessor-discipline.md)
94+
is amended — its "`@dbColumnType` is the *only* attribute deliberately read own-only" line was
95+
false as written no matter which way this ruled, since the JVM own-reads already existed.
96+
`@provided` is now chartered as the second, with an explicit note that the member set it
97+
accompanies (`@values`, and its numeric half `@intValueMap`) stays **resolving**.
98+
99+
The pin is proven non-vacuous: reverting just the TypeScript half turns the chained-declaration
100+
case red and leaves the other six green. A shared conformance fixture is deliberately withheld —
101+
adding a chained-declaration case surfaced a *second*, deeper divergence (Kotlin names a chained
102+
abstract enum after the top-most root, so it holds that the alias **is** its parent while every
103+
other port holds it is its own type) that needs a design ruling of its own rather than pinning
104+
one port's accidental behaviour.
105+
48106
### Fixed — a requirement could not claim a prompt template (npm)
49107

50108
`@implementedBy` is documented as naming "the model nodes realising this requirement", and

CLAUDE.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ Equal weight — all four ship per-language today across the five ports (TS / C#
1717

1818
## Status
1919

20-
_Last refreshed 2026-08-15._
20+
_Last refreshed 2026-08-16._
2121

22-
**TypeScript reference implementation** is **published to npm at `0.23.0`** (14 `@metaobjectsdev/*` publish candidates on the `latest` tag, full lockstep; the two `angular` packages are versioned on their own `0.6.x` line and are **NOT published to npm — source-only by decision** (ADR-0048): they build in-repo, and stay off the registry until they meet the ADR's promotion bar). C# at `0.23.0` (NuGet); Python at `0.23.0` (PyPI); Java / Kotlin at `7.23.0` (Maven Central). **All four registries share the same `minor.patch`, and stay locked in lockstep going forward** — the `0.20.15` cut moved Maven `7.20.12``7.20.15` to close the last per-registry drift.
22+
**TypeScript reference implementation** is **published to npm at `0.23.1`** (14 `@metaobjectsdev/*` publish candidates on the `latest` tag, full lockstep; the two `angular` packages are versioned on their own `0.6.x` line and are **NOT published to npm — source-only by decision** (ADR-0048): they build in-repo, and stay off the registry until they meet the ADR's promotion bar). C# at `0.23.1` (NuGet); Python at `0.23.1` (PyPI); Java / Kotlin at `7.23.1` (Maven Central). **All four registries share the same `minor.patch`, and stay locked in lockstep going forward** — the `0.20.15` cut moved Maven `7.20.12``7.20.15` to close the last per-registry drift.
23+
24+
**The `0.23.1` line is a coordinated PATCH across all four registries** (npm `0.23.1` · PyPI `0.23.1` · NuGet `0.23.1` · Maven `7.23.1`) — every registry carries a real changed product file, so none is a version-parity bump. **The theme is a check that was confidently wrong:** not one of these is a missing feature; each is a guarantee the toolchain already made and quietly failed to keep. (1) **`{{#hasField}}` rendered as absent on a populated payload, in every port.** A prompt's conditional section is a DERIVED boolean over a declared field; the JVM has emitted `has<Field>()` onto generated payload records since 7.7.7 and accepted the section in `verify` — **no render engine implemented the other half**, so given the same payload *data* (a map, which is what the runtime and the corpus actually pass) all five ports dropped the block's contents silently, with no error. The JVM looked correct only because a generated RECORD answers `hasFoo()` by its own method, so the same payload rendered differently depending on whether it arrived as a record or as a map — the byte-identical-rendering promise failing quietly. `PayloadAccessors` now exists in all five ports with one naming rule and the JVM emitter's presence semantics (string → non-blank, collection → non-empty, reference → non-null, **number/boolean → no accessor**, since `{{#hasCount}}` over an int is drift rather than a conditional); an AUTHORED `hasFoo` always wins. Found by an adopter whose Node gate reported **157** `ERR_VAR_NOT_ON_PAYLOAD` against a JVM gate reporting none — now 0 on both. (2) **`@provided` flowed down an `extends` chain in TS/C#/Python** — it is a provenance fact about the declaration ITSELF, like `abstract`, so it must not be inherited; the resolving ports emitted a reference to a hand-written type **the adopter never declared**. ADR-0039 amended (`@provided` is chartered as the second deliberately own-only attribute; the member set it accompanies stays RESOLVING). (3) **`@verifiedBy` stopped deciding what a test file is** — its closed pattern list was a guess about someone else's repository and was wrong on Maven Failsafe (`*IT`) from the day it shipped, convicting a correct project with `ERR_REQUIREMENT_TEST_MISSING`; Failsafe's defaults are now built in, `verify.testFiles` lets a project declare its own, and an unrecognised convention WARNs instead of convicting. (4) **A requirement can now claim a `template.prompt`** — L4 means "a declared top-level model node", so the estate with the most to gain from a status (a retired prompt leaves nothing behind) can finally carry one. (5) **`verify` gates the committed schema snapshot** ([#292](https://github.com/metaobjectsdev/metaobjects/issues/292)) — that file decides what DDL the next migration contains and nothing checked it; the check is conditioned on metadata==DB, which is what makes it false-positive-free. (6) **Codegen and migrate named the same CHECK constraint two different ways** ([#293](https://github.com/metaobjectsdev/metaobjects/issues/293)) — codegen changed, not migrate, because migrate's suffix names are already in live databases. **The durable lesson is the shape they share: each survived because the thing that would have caught it did not exist** — the render corpus had NO fixture using a derived accessor at all, nothing ever compared codegen's constraint name to migrate's, and no gate read the snapshot. Every fix ships with the missing check, not just the corrected behaviour. See `CHANGELOG.md` [0.23.1].
2325

2426
**The `0.23.0` line is a coordinated MINOR across all four registries** (npm `0.23.0` · PyPI `0.23.0` · NuGet `0.23.0` · Maven `7.23.0`) — **the requirement vocabulary gains a second axis: what was DECIDED about a gap.** Cut as MINOR because it adds registered vocabulary and pre-1.0 `^0.22.x` resolves `<0.23.0`, so a consumer adopts it deliberately; everything in it is additive, and a project declaring no `requirement.*` nodes sees no change at all. `@status: partial` said there IS a gap but never what anyone decided about it, so a known-and-tolerated gap and a gap nobody had looked at were the same value. Three additions close that, on **both** subtypes in all five ports: **`@status: planned`** (intended, not built — its `@implementedBy` may dangle, it is exempt from the architectural universality check, and it **never counts toward object coverage**, because otherwise the cheapest way to clear an unclaimed-entity warning would be to declare an intention and the gate would measure ambition rather than work); **`@disposition`** (`accepted | deferred`, where **absent means UNDECIDED** — folding this into `@status` would make "there is a gap" and "we chose to live with it" the same fact); and **`@trackedBy`** (issue references, free-form and deliberately **never resolved** — `verify` has no network, and which sprint/who owns it lives in the tracker because two systems holding that answer will drift). Two new warnings: a `@disposition` on a status with no outstanding work, and `deferred` naming no ticket. **`meta verify` now prints a requirements summary on EVERY run** — a gate that says nothing when it passes cannot be told apart from a gate that checked nothing. **Architectural requirements can now nest**: `functional` declared a `requirement.*` child rule and `architectural` declared none, so an architectural node could nest under a *functional* parent but never under another architectural one, making a quality taxonomy inexpressible; **`@level` is now OPTIONAL on `architectural`** (absent keeps the flat object-independent form, so every existing ledger stays valid; present opts into a tree and the same nesting + link-floor rules apply). The levelling guidance is corrected everywhere it appears: **L1–L3 are levels of abstraction and ownership in the problem domain, never a directory, package, deployable or module** — if a behaviour-preserving refactor would force a node to move, its level is wrong. Separately, **`description` and `notes` are re-split by CONTENT KIND, not audience** (`description` = what the element is and covers, derivable from the model; `notes` = what you had to look outside the model to learn — *a sentence belongs in `notes` exactly when it would have to change because the IMPLEMENTATION changed while the model did not*): the old audience split made writing the same thing twice the honest way to fill both, and dogfooding put a number on it — **overlap on 72 of 245 entries**, the same overlap every time. **The cut also fixed a `main` that had been RED on four of five ports** since the vocabulary landed in TypeScript and in the byte-gated `expected-registry.json` without the port registrations — nobody ran the gate, because those lanes are release-tag/dispatch-only and `scripts/ci-local.sh` piped through `tail` reports the exit status of `tail`. See `CHANGELOG.md` [0.23.0].
2527

@@ -56,7 +58,7 @@ _Last refreshed 2026-08-15._
5658

5759
**Key cross-language features shipped:** FR5 family (a/b/c/d/e + WARN envelope-shape — actionable loader errors per ADR-0009); FR-003 (Java RDB runtime persistence + projections; schema migrations are TS-only — the Java migration engine was removed); FR-006 (template.output parser-on-receipt codegen per ADR-0010 in all 5 ports); FR-008 + FR-009 (cross-port REST API contract + 10 filter operators); FR-018 (M:N relationship codegen in all 5 ports — entity navigation + idiomatic ORM wiring [Drizzle m2m / EF Core `UsingEntity` / Spring repo+JPA / Exposed / Pydantic+route as the SQLAlchemy-secondary equivalent] + REST traversal `GET /<source-plural>/{id}/<relation>` + Tier-2 docs, gated by the shared api-contract m2m corpus in both lanes + persistence-conformance; the TanStack M:N client hook is a deferred client-ergonomics follow-up); SP-H (field-subtype end-to-end hardening: every concrete `field.*` subtype write+read round-trips cross-port via the persistence `op: roundtrip` gate; cut `field.byte`/`field.short`/`field.class` non-functional stubs; cross-port filter-op reconciliation for uuid/currency); source v2 paradigm (ADR-0007); metadata-ktx Kotlin facade; per-target output directories (TS codegen).
5860

59-
**Current release line.** `0.21.x` (npm / PyPI / NuGet, version-aligned) · `7.21.x` (Maven Central, aligned `minor.patch` on its historical major `7`). The `0.15.0` / `7.7.0` release is a coordinated **minor with breaking changes**: (1) the **metamodel-1.0 vocabulary program** — `field.uri` / `field.inet` subtypes + a `@stringFormat` attribute (ADR-0036/0037), `field.timestamp` **instant-by-default** with an `@localTime` naive opt-out, `@dbColumnType` **slim-and-derive** (array-ness + `text` derived; `uuid_array`/`text_array`/`@kind:text` dropped), and reverse-navigation via generated explicit FK finders (ADR-0038); (2) the **ADR-0039 own-accessor correctness fix** — resolving/effective accessors are the default everywhere, so a concrete field/entity that `extends` an abstract parent now correctly inherits its properties and members in codegen + runtime (an entire latent bug class, cross-port conformance-gated). Breaking: instant-default timestamps and the `@dbColumnType` slim. See `CHANGELOG.md` for the per-version detail. GA promotion is the next release move.
61+
**Current release line.** `0.23.x` (npm / PyPI / NuGet, version-aligned) · `7.23.x` (Maven Central, aligned `minor.patch` on its historical major `7`). The `0.15.0` / `7.7.0` release is a coordinated **minor with breaking changes**: (1) the **metamodel-1.0 vocabulary program** — `field.uri` / `field.inet` subtypes + a `@stringFormat` attribute (ADR-0036/0037), `field.timestamp` **instant-by-default** with an `@localTime` naive opt-out, `@dbColumnType` **slim-and-derive** (array-ness + `text` derived; `uuid_array`/`text_array`/`@kind:text` dropped), and reverse-navigation via generated explicit FK finders (ADR-0038); (2) the **ADR-0039 own-accessor correctness fix** — resolving/effective accessors are the default everywhere, so a concrete field/entity that `extends` an abstract parent now correctly inherits its properties and members in codegen + runtime (an entire latent bug class, cross-port conformance-gated). Breaking: instant-default timestamps and the `@dbColumnType` slim. See `CHANGELOG.md` for the per-version detail. GA promotion is the next release move.
6062

6163
See `spec/roadmap.md` for the active + planned work picture.
6264

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ first-week wedge plan — and `meta init` picks up from there.
9393

9494
| Language | Status | Quickstart | Source |
9595
|---|---|---|---|
96-
| TypeScript | Published to npm at `0.23.0` (the `@metaobjectsdev/*` packages) | [`docs/ports/typescript.md`](docs/ports/typescript.md) | [`server/typescript/`](server/typescript/) · [`client/web/`](client/web/) |
96+
| TypeScript | Published to npm at `0.23.1` (the `@metaobjectsdev/*` packages) | [`docs/ports/typescript.md`](docs/ports/typescript.md) | [`server/typescript/`](server/typescript/) · [`client/web/`](client/web/) |
9797
| Java | Loader + OMDB + render + Maven plugin all shipped; full conformance green | [`docs/ports/java.md`](docs/ports/java.md) | [`server/java/`](server/java/) |
9898
| Kotlin | Codegen tier on top of Java — 14 generators (entity, Exposed table, relations, repository, payload, output-parser, output-prompt, render-helper, extractor, filter-allowlist, validator, Spring config, storedProc, Spring controller); 24 / 24 persistence-conformance | [`docs/ports/kotlin.md`](docs/ports/kotlin.md) | [`server/java/codegen-kotlin/`](server/java/codegen-kotlin/) · [`server/java/metadata-ktx/`](server/java/metadata-ktx/) |
9999
| C# | Loader + conformance + EF Core codegen + render engine + `dotnet meta` CLI all shipped | [`docs/ports/csharp.md`](docs/ports/csharp.md) | [`server/csharp/`](server/csharp/) |

0 commit comments

Comments
 (0)