Skip to content

Commit 62c333f

Browse files
dmealingclaude
andcommitted
feat(conformance): SP-G Java recon Unit8 — re-enable Java+Kotlin registry gates (all 5 ports green); constrain Kotlin runner to metamodel provider set; docs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 27fa396 commit 62c333f

7 files changed

Lines changed: 178 additions & 62 deletions

File tree

.github/workflows/conformance.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ jobs:
4949
cd server/typescript/packages/metadata && bun test test/conformance.test.ts test/yaml-conformance.test.ts test/object-model-conformance.test.ts
5050
# Registry-conformance gate (TS is the reference emitter — byte-matches
5151
# the committed canonical) + untested-vocabulary coverage report.
52-
# C#/Python run theirs via the whole-project/whole-dir scope below;
53-
# Java/Kotlin's runner is @Disabled (pending the Java reconciliation, #36)
54-
# and is intentionally NOT added to their scoped -Dtest= lists.
52+
# All five ports run the gate now (SP-G reconciliation landed): C#/Python
53+
# via the whole-project/whole-dir scope below; Java + Kotlin via the
54+
# RegistryManifestConformanceTest added to their scoped -Dtest= lists.
5555
bun test test/registry-conformance.test.ts test/registry-coverage.test.ts
5656
# Byte-exact render / verify / extract / output-prompt corpora (packages/render).
5757
cd ../render && bun test test/render-conformance.test.ts test/verify-conformance.test.ts test/extract/extract-conformance.test.ts test/output-prompt-conformance.test.ts
@@ -83,8 +83,9 @@ jobs:
8383
# ("No type registered for: field.uuid") when the render module's tests
8484
# reference the freshly-built metadata artifact.
8585
mvn -pl metadata,render,codegen-spring -am install -DskipTests -q
86-
# Loader + YAML + object-model corpora (metadata module).
87-
mvn -pl metadata test -Dtest='ConformanceTest,YamlConformanceTest,ObjectModelConformanceTest' -q
86+
# Loader + YAML + object-model corpora (metadata module)
87+
# + registry-conformance gate (metamodel-vocabulary manifest byte-match, SP-G).
88+
mvn -pl metadata test -Dtest='ConformanceTest,YamlConformanceTest,ObjectModelConformanceTest,RegistryManifestConformanceTest' -q
8889
# Byte-exact render / verify / extract / output-prompt corpora (render module).
8990
mvn -pl render test -Dtest='RenderCrossPortReportTest,VerifyConformanceTest,ExtractConformanceTest,OutputPromptConformanceTest' -q
9091
# Validator-parity corpus (generated DTO jakarta.validation, codegen-spring module)
@@ -128,8 +129,10 @@ jobs:
128129
# artifacts.
129130
mvn -pl codegen-kotlin -am install -DskipTests -q
130131
# object-model + output-prompt + validator-parity corpora (the corpora Kotlin has)
131-
# + generator stable-name registry conformance (ADR-0021 D3).
132-
mvn -pl codegen-kotlin test -Dtest='ObjectModelConformanceTest,OutputPromptConformanceTest,ValidationConformanceTest,GeneratorRegistryConformanceTest' -q
132+
# + generator stable-name registry conformance (ADR-0021 D3)
133+
# + registry-conformance gate (metamodel-vocabulary manifest byte-match, SP-G;
134+
# composes the metamodel provider set so codegen-base/om classpath SPI does not pollute it).
135+
mvn -pl codegen-kotlin test -Dtest='ObjectModelConformanceTest,OutputPromptConformanceTest,ValidationConformanceTest,GeneratorRegistryConformanceTest,RegistryManifestConformanceTest' -q
133136
134137
completeness-gate:
135138
needs: [conformance, conformance-kotlin]

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ import { CurrencyInput } from "@metaobjectsdev/react";
406406

407407
Preserve the following contracts exactly across all language ports:
408408

409-
**Metamodel subtype vocabularies (must be identical across languages):** the `registry-conformance` gate (`fixtures/registry-conformance/`) is the structural enforcer of this rule — each port emits its registry as a canonical manifest byte-matched to `expected-registry.json` (TS/C#/Python live + green; Java/Kotlin gated pending reconciliation). See `fixtures/registry-conformance/README.md`.
409+
**Metamodel subtype vocabularies (must be identical across languages):** the `registry-conformance` gate (`fixtures/registry-conformance/`) is the structural enforcer of this rule — each port emits its registry as a canonical manifest byte-matched to `expected-registry.json`. **All five ports (TS / C# / Java / Kotlin / Python) are live + green** (SP-G Java/Kotlin reconciliation complete; the JVM runners compose from the defined metamodel provider set so codegen-base/om classpath SPI does not pollute the measured vocabulary). See `fixtures/registry-conformance/README.md`.
410410
- Filter operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isNull`
411411
- Source subtypes: `rdb` (paradigm; ADR-0007). The pre-v2 `dbTable`/`dbView` subtypes are RETIRED — `source.rdb` + `@kind: table|view|materializedView|storedProc|tableFunction` is the form, with read-only-ness derived from `@kind`. Multi-source via `@role` (exactly one `primary` per object). Source physical name = `@table` (NOT `@name`); field physical name = `@column` (renamed from `@dbColumn`). Referential actions on relationships: `@onDelete` / `@onUpdate`.
412412
- Origin subtypes: `passthrough`, `aggregate`

docs/superpowers/specs/2026-06-02-sp-g-java-registry-divergence-analysis.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
# SP-G — Java registry divergence classification (A/B/C/D buckets)
22

3-
_Analysis only. No code/registration/canonical changes were made. This document
4-
drives the decision of whether to (a) fix Java, (b) expand the cross-port
5-
contract, or (c) treat divergences as out-of-scope._
3+
**STATUS: RECONCILED — Java/Kotlin gates re-enabled (SP-G Unit 8).** Every
4+
divergence classified below was reconciled at source across SP-G Units 2-7 (the
5+
Phase-1 emitter exclusions + the Phase-2 vocabulary convergence). The Java
6+
manifest now byte-matches the canonical; SP-G Unit 8 re-enabled both JVM runners
7+
(`RegistryManifestConformanceTest` in `metadata` + `codegen-kotlin`), constrained
8+
them to the defined metamodel provider set (`RegistryManifest.composeMetamodelRegistry()`,
9+
so the `codegen-kotlin` classpath's `om` + `codegen-base` SPI providers no longer
10+
pollute the measured vocabulary), and wired both into `.github/workflows/conformance.yml`.
11+
All five ports (TS / C# / Java / Kotlin / Python) are now live + green on the gate.
12+
The analysis below is retained as the historical classification record.
13+
14+
_Originally analysis only — no code/registration/canonical changes at authoring
15+
time. This document drove the decision of whether to (a) fix Java, (b) expand the
16+
cross-port contract, or (c) treat divergences as out-of-scope._
617

718
## Method
819

fixtures/registry-conformance/README.md

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,29 @@ divergence:
199199
| TypeScript | **live + green** (reference emitter; produces the canonical) | `packages/metadata/test/registry-conformance.test.ts` | `conformance` job, `typescript` matrix leg (scoped `bun test`) |
200200
| C# | **live + green** (byte-identical) | `MetaObjects.Conformance.Tests/RegistryManifestConformanceTests.cs` | `conformance` job, `csharp` matrix leg (whole `MetaObjects.Conformance.Tests` project) |
201201
| Python | **live + green** (byte-identical) | `tests/conformance/test_registry_conformance.py` | `conformance` job, `python` matrix leg (whole `tests/conformance` dir) |
202-
| Java | **gated (`@Ignore`)** — Phase-2 vocabulary divergence | `metadata/src/test/java/com/metaobjects/registry/RegistryManifestConformanceTest.java` | n/a — intentionally NOT in the scoped `-Dtest=` list while gated |
203-
| Kotlin | **gated (`@Disabled`)** — shares the Java JVM registry | `codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/RegistryManifestConformanceTest.kt` | n/a — intentionally NOT in the scoped `-Dtest=` list while gated |
202+
| Java | **live + green** (byte-identical; reconciled SP-G Units 4-7, gate re-enabled Unit 8) | `metadata/src/test/java/com/metaobjects/registry/RegistryManifestConformanceTest.java` | `conformance` job, `java` matrix leg (in the metadata `-Dtest=` list) |
203+
| Kotlin | **live + green** (byte-identical; composes the metamodel provider set) | `codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/RegistryManifestConformanceTest.kt` | `conformance-kotlin` job (in the codegen-kotlin `-Dtest=` list) |
204204

205-
The three live ports (TS / C# / Python) genuinely run on every CI build (`.github/workflows/conformance.yml`, `conformance` job). The Java + Kotlin runners are wired and compile, but their assertions are `@Ignore`/`@Disabled` pending the Java metamodel-vocabulary reconciliation (tracked as a dedicated follow-on; see the **divergence analysis**:
205+
All five ports now genuinely run the gate on every CI build (`.github/workflows/conformance.yml`). TS / C# / Python were live from the start; Java + Kotlin were re-enabled in SP-G Unit 8 after the Java metamodel-vocabulary reconciliation (Units 4-7) landed (see the **divergence analysis**:
206206
[`docs/superpowers/specs/2026-06-02-sp-g-java-registry-divergence-analysis.md`](../../docs/superpowers/specs/2026-06-02-sp-g-java-registry-divergence-analysis.md) and the
207-
[reconciliation plan](../../docs/superpowers/plans/2026-06-02-sp-g-java-reconciliation-plan.md)). They are left out of the Java/Kotlin scoped `-Dtest=` lists so they neither run nor error — re-add them (and drop the annotation) once the reconciliation lands.
207+
[reconciliation plan](../../docs/superpowers/plans/2026-06-02-sp-g-java-reconciliation-plan.md)).
208+
209+
**Why the Java + Kotlin runners compose from a defined provider set.** Both JVM
210+
runners build their registry from the explicit metamodel provider set
211+
(`RegistryManifest.composeMetamodelRegistry()` — mirroring the TS reference's
212+
`composeRegistry(coreProviders)`), NOT from the process-global
213+
`MetaDataRegistry.getInstance()` singleton. The metadata module's classpath holds
214+
only the metamodel providers, but a downstream module that also runs the gate
215+
(`codegen-kotlin`) has the `om` + `codegen-base` modules on its test classpath —
216+
whose SPI providers register an extra `object.managed` subtype and ~22
217+
codegen-tooling attrs (`ai*` / `json*` / `has*`, self-registered by the doc
218+
generators `MetaDataAIDocumentationGenerator` / `MetaDataFileJsonSchemaGenerator`).
219+
Those are per-port codegen tooling, NOT the cross-port logical metamodel
220+
vocabulary the gate measures (the same category as the EXCLUDED native type
221+
bindings / codegen targets). Composing from the defined provider set makes every
222+
module's runner measure the identical vocabulary — the gate stays meaningful (it
223+
still catches a real attr/subtype divergence in any metamodel provider) while
224+
being immune to classpath pollution.
208225

209226
Detail per port:
210227

@@ -230,11 +247,13 @@ Detail per port:
230247
never-registered constants (`RELATIONSHIP_ATTR_FK_FIELD` / `_PARENT_FIELD`)
231248
were removed. No structural (Java-class) divergence — Python uses the same attr
232249
vocabulary and registry model.
233-
- **Java + Kotlin (shared JVM registry)** — emitter (`RegistryManifest.emit`,
250+
- **Java + Kotlin (shared JVM emitter)** — emitter (`RegistryManifest.emit`,
234251
`metadata` module) + runners (`RegistryManifestConformanceTest` in `metadata`
235-
and `codegen-kotlin`) are wired and functional, but the assertions remain
236-
**`@Ignore`/`@Disabled` — ESCALATED** (Phase-2 divergences below remain).
237-
SP-G Phase 1 (Units 2-3) settled three of the divergences cross-port via the
252+
and `codegen-kotlin`) are **live + green** (both re-enabled in SP-G Unit 8;
253+
each composes from the defined metamodel provider set — see "Why the Java +
254+
Kotlin runners compose from a defined provider set" above). The historical
255+
divergence inventory below records what the gate surfaced and how it was
256+
reconciled. SP-G Phase 1 (Units 2-3) settled three of the divergences cross-port via the
238257
uniform emitter exclusions documented in "EXCLUDED" above (no Java behavior
239258
change): the structural keywords `isArray`/`isAbstract` + the `description`
240259
per-type duplicate are filtered from the `attrs` list, the `metadata.base`
@@ -267,8 +286,9 @@ Detail per port:
267286
+ owned-object `@storage="jsonb"`, with consumers in `omdb` + `codegen-mustache`
268287
migrated; the DDL/migration-only remnants `dbForeignKey`/`previousName`/
269288
`dbIndexName`/`dbSequenceName`/`dbTablespace` dropped as dead under ADR-0015).
270-
The Java manifest now byte-matches the canonical (residual EMPTY); the gate stays
271-
`@Ignore`/`@Disabled` only until SP-G Unit 8 flips it atomically with CI wiring.
289+
The Java manifest now byte-matches the canonical (residual EMPTY); SP-G Unit 8
290+
re-enabled the gate (dropped `@Ignore`/`@Disabled`), constrained both JVM
291+
runners to the defined metamodel provider set, and wired both into CI.
272292

273293
## Regenerating the canonical
274294

server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/RegistryManifestConformanceTest.kt

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,56 @@
11
package com.metaobjects.generator.kotlin
22

3-
import com.metaobjects.registry.MetaDataRegistry
43
import com.metaobjects.registry.RegistryManifest
54
import java.nio.charset.StandardCharsets
65
import java.nio.file.Files
76
import java.nio.file.Path
87
import kotlin.test.Test
98
import kotlin.test.assertEquals
109
import kotlin.test.assertTrue
11-
import org.junit.jupiter.api.Disabled
1210

1311
/**
1412
* SP-G Registry Conformance — the Kotlin runner.
1513
*
16-
* Kotlin runs on the JVM and REUSES the shared Java [MetaDataRegistry] and the Java
17-
* [RegistryManifest] emitter (the same classes the Java runner validates). This test obtains
18-
* the shared JVM registry and asserts the emitted manifest is byte-identical to the single
19-
* committed canonical `fixtures/registry-conformance/expected-registry.json` — proving the
20-
* JVM-view of the registry matches the cross-port contract from the Kotlin module too.
14+
* Kotlin runs on the JVM and REUSES the shared Java [RegistryManifest] emitter (the same
15+
* class the Java runner validates). This test composes a registry from the DEFINED metamodel
16+
* provider set ([RegistryManifest.composeMetamodelRegistry]) and asserts the emitted manifest
17+
* is byte-identical to the single committed canonical
18+
* `fixtures/registry-conformance/expected-registry.json` — proving the metamodel vocabulary
19+
* matches the cross-port contract from the Kotlin module too.
2120
*
22-
* Byte-identity to the Java runner holds by construction (same registry, same emitter); the
23-
* value here is the gate existing in the Kotlin module's test surface as well.
21+
* It composes from the metamodel provider set rather than the process-global
22+
* `MetaDataRegistry.getInstance()` ON PURPOSE: this module's test classpath also carries the
23+
* `om` + `codegen-base` SPI providers, which register an extra `object.managed` subtype and
24+
* ~22 codegen-tooling attrs (`ai*`/`json*`/`has*`, self-registered by the doc generators).
25+
* Those are per-port codegen tooling, not the cross-port logical metamodel vocabulary this
26+
* gate measures. Composing from the defined provider set makes this runner measure exactly
27+
* what the metadata-module runner does — immune to classpath pollution while still catching
28+
* real metamodel-vocabulary drift. See `fixtures/registry-conformance/README.md`.
29+
*
30+
* Byte-identity to the Java runner holds by construction (same provider set, same emitter);
31+
* the value here is the gate existing in the Kotlin module's test surface as well.
2432
*
2533
* Like the Java runner, this is a drift-finding gate: any mismatch is a real divergence
2634
* between the JVM metamodel registry's logical vocabulary and the cross-port canonical. Fix
2735
* the Java registration to match the canonical (TS is the reference) — do NOT loosen the
2836
* canonical. Escalate if TS itself is wrong versus the documented vocabulary.
29-
*
30-
* `@Disabled` — ESCALATED, same root cause as the Java runner
31-
* (`com.metaobjects.registry.RegistryManifestConformanceTest`): the shared JVM registry's
32-
* attribute vocabulary diverges pervasively from the cross-port canonical that TS, C#, and
33-
* Python agree on. See that Java test's Javadoc for the full divergence inventory. The
34-
* canonical is correct and is NOT edited; re-enable once the Java vocabulary reconciliation
35-
* lands.
3637
*/
3738
class RegistryManifestConformanceTest {
3839

3940
@Test
40-
@Disabled(
41-
"SP-G ESCALATED: the shared JVM metamodel registry vocabulary diverges pervasively " +
42-
"from the cross-port canonical (TS/C#/Python agree). See the Java runner's Javadoc " +
43-
"for the inventory. Re-enable once the Java vocabulary reconciliation lands; the " +
44-
"canonical is correct and is NOT to be edited.",
45-
)
4641
fun manifestMatchesCanonical() {
47-
val registry = MetaDataRegistry.getInstance()
42+
// Compose from the DEFINED metamodel provider set (RegistryManifest.
43+
// composeMetamodelRegistry) rather than the process-global
44+
// MetaDataRegistry.getInstance(): this module's test classpath ALSO
45+
// carries the `om` + `codegen-base` SPI providers, which register an
46+
// extra `object.managed` subtype and ~22 codegen-tooling attrs
47+
// (`ai*`/`json*`/`has*`, self-registered by the doc generators). Those
48+
// are per-port codegen tooling, NOT the cross-port logical metamodel
49+
// vocabulary this gate measures. Composing from the metamodel provider
50+
// set makes the Kotlin runner measure exactly what the metadata-module
51+
// runner does — byte-identical to the canonical, and still catching real
52+
// metamodel-vocabulary drift.
53+
val registry = RegistryManifest.composeMetamodelRegistry()
4854
val got = RegistryManifest.emit(registry).replace("\r\n", "\n")
4955
val want = readCanonical().replace("\r\n", "\n")
5056

0 commit comments

Comments
 (0)