Skip to content

Commit eac195d

Browse files
committed
Merge remote-tracking branch 'origin/main' into worktree-fr-004-plan-3c
2 parents f8ff8d8 + 12e4cae commit eac195d

87 files changed

Lines changed: 3338 additions & 1397 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.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Equal weight. Three ship per-language today; the fourth is committed for 7.0.0:
1717

1818
## Status
1919

20-
TypeScript reference implementation is **published to npm at `0.5.0`** (11 `@metaobjectsdev/*` packages on the `latest` tag; `cli` at `0.5.1`) — Projects D–G shipped end-to-end with 2124+ tests passing. Java port is in progress: H3a (loader restructure) shipped 2026-05-19; H3b (conformance harness) is active. **C# loader + conformance shipped** (loader, canonical serializer, and a `dotnet test` conformance runner that runs the full shared corpus; codegen + runtime remain out of scope for C#). Python is planned post-H3.
20+
TypeScript reference implementation is **published to npm at `0.5.0`** (11 `@metaobjectsdev/*` packages on the `latest` tag; `cli` at `0.5.1`) — Projects D–G shipped end-to-end with 2124+ tests passing. Java port is in progress: H3a (loader restructure) shipped 2026-05-19; H3b (conformance harness) is active. **C# loader + conformance shipped** (loader, canonical serializer, and a `dotnet test` conformance runner that runs the full shared corpus; metadata layer fully caught up — empty expected-failures ledger, incl. `template.*` + `origin.collection`). C# is now also growing the FR-004 codegen/runtime tier: the **render engine (`MetaObjects.Render`) ships** and renders the shared `fixtures/render-conformance` corpus byte-identical to TS; payload-VO codegen + `verify` are in progress. (This supersedes the earlier "codegen + runtime out of scope for C#" boundary, which dated from when C# was the stale laggard.) Python is planned post-H3.
2121

2222
The 7.0.0 line is specced: FR-003 brings the Java OMDB persistence engine, metadata-driven schema migration, and dynamic projections onto current core; FR-004 builds the fourth pillar (cross-language prompt construction) on top of FR-003's projections. Both are design-stage plan-of-record (`docs/superpowers/specs/2026-05-22-fr-003-*` and `*-fr-004-*`), not yet implemented.
2323

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# WA1 — Case-preserving (case-sensitive) registry — Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Make the Java metamodel registry **case-sensitive on the exact (canonical) type/subtype vocabulary** — remove the type/subtype lowercasing in `MetaDataTypeId` + `ChildRequirement` — so the standard's camelCase subtypes (`dbTable`, `dbView`, `dataGrid`) are matched and round-tripped faithfully, and non-canonical-case input is rejected rather than silently accepted.
6+
7+
**Architecture:** The lowercasing lives only in the **registry lookup key** (`MetaDataTypeId`, which keys the type-definition map + pattern matching) and **placement rules** (`ChildRequirement`). Node instances already store their subtype verbatim (`createInstance` passes the verbatim args; node classes hardcode their canonical subtype constant), so the canonical *output* casing comes from the registered constants — not the key. Removing the lowercasing makes lookups exact-match (case-sensitive), which (a) rejects non-canonical input instead of silently accepting it, and (b) eliminates the latent path where a parsed wrong-case subtype could leak to canonical output. Foundational for WA3 (camelCase `source.dbView`/`dbTable`).
8+
9+
**Tech Stack:** Java 21, Maven `metadata` module under `server/java/`, JUnit4.
10+
11+
**Spec:** `docs/superpowers/specs/2026-05-23-java-standard-alignment-and-loader-consolidation-design.md` (WA1). This is the first of several plans from that spec; WA2/WA3/WA4 follow.
12+
13+
**Scope note:** Today Java's entire registered vocabulary is single-word **lowercase** (`string`, `int`, `pojo`, `base`, …), which is already canonical — so removing the lowercasing is largely a **no-op for current types** and a **strictness + future-camelCase enabler**. The audit (Task 4) should therefore find few or no breakages; any it finds are genuine non-canonical-case usages to correct.
14+
15+
**Leave-list (do NOT change — these lowercase *names*/provider-ids, not type-keys):** `CanonicalJsonSerializer:439-440` (auto-name prefix for unnamed nodes), `BaseMetaDataParser:276-277` (auto-name prefix), `MetaField.java:383` (`switch(subType.toLowerCase())` — harmless: field subtypes are canonically lowercase), `MetaDataTypeProvider:93,97` (provider-id derivation).
16+
17+
**Worktree:** execute in this worktree (`java-casing-fix`); integrate by merging forward into `main` (never rewrite main).
18+
19+
---
20+
21+
## Task 1: `MetaDataTypeId` — case-sensitive key + pattern matching
22+
23+
**Files:**
24+
- Modify: `server/java/metadata/src/main/java/com/metaobjects/MetaDataTypeId.java` (lines 43-44, 68-69, 104)
25+
- Test: `server/java/metadata/src/test/java/com/metaobjects/MetaDataTypeIdCaseTest.java`
26+
27+
- [ ] **Step 1: Write the failing test**
28+
29+
```java
30+
package com.metaobjects;
31+
32+
import org.junit.Test;
33+
import static org.junit.Assert.*;
34+
35+
public class MetaDataTypeIdCaseTest {
36+
37+
@Test public void preserves_camelCase_subtype() {
38+
MetaDataTypeId id = new MetaDataTypeId("source", "dbView");
39+
assertEquals("source", id.type());
40+
assertEquals("dbView", id.subType()); // not "dbview"
41+
assertEquals("source.dbView", id.toQualifiedName());
42+
}
43+
44+
@Test public void differs_by_case_is_not_equal() {
45+
assertNotEquals(new MetaDataTypeId("source", "dbView"),
46+
new MetaDataTypeId("source", "dbview"));
47+
}
48+
49+
@Test public void wildcard_pattern_matches_case_sensitively() {
50+
MetaDataTypeId id = new MetaDataTypeId("source", "dbView");
51+
assertTrue(id.matches("source.*"));
52+
assertTrue(id.matches("source.dbView"));
53+
assertFalse(id.matches("source.dbview")); // exact case required
54+
assertFalse(id.matches("Source.dbView")); // type case too
55+
}
56+
}
57+
```
58+
59+
- [ ] **Step 2: Run, verify it fails**
60+
61+
Run: `cd server/java && mvn -o -pl metadata test -Dtest=MetaDataTypeIdCaseTest`
62+
Expected: `preserves_camelCase_subtype` fails (`subType()` returns `"dbview"`); `differs_by_case_is_not_equal` fails (both lowercase to equal); `matches("source.dbview")` returns true (should be false).
63+
64+
- [ ] **Step 3: Remove the lowercasing (keep `trim()`)**
65+
66+
In `MetaDataTypeId.java`, the compact constructor (lines ~43-44):
67+
68+
```java
69+
type = type.trim();
70+
subType = subType.trim();
71+
```
72+
73+
In `matches(String pattern)` (lines ~68-69):
74+
75+
```java
76+
String patternType = parts[0].trim();
77+
String patternSubType = parts[1].trim();
78+
```
79+
80+
In `pattern(String type, String subType)` (line ~104):
81+
82+
```java
83+
return new MetaDataTypeId(type.trim(), subType.trim());
84+
```
85+
86+
(Do not touch `fromQualifiedName` — it delegates to the constructor. Do not touch `matches(MetaDataTypeId)` — it already compares `type`/`subType` directly.)
87+
88+
- [ ] **Step 4: Run, verify pass**
89+
90+
Run: `cd server/java && mvn -o -pl metadata test -Dtest=MetaDataTypeIdCaseTest`
91+
Expected: `Tests run: 3, Failures: 0, Errors: 0`.
92+
93+
- [ ] **Step 5: Commit**
94+
95+
```bash
96+
git add server/java/metadata/src/main/java/com/metaobjects/MetaDataTypeId.java \
97+
server/java/metadata/src/test/java/com/metaobjects/MetaDataTypeIdCaseTest.java
98+
git commit -m "fix(metadata): MetaDataTypeId case-sensitive on canonical type/subtype (no lowercasing)"
99+
```
100+
(End the commit message with the trailer line `Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>`.)
101+
102+
---
103+
104+
## Task 2: `ChildRequirement` — case-sensitive placement keys
105+
106+
**Files:**
107+
- Modify: `server/java/metadata/src/main/java/com/metaobjects/registry/ChildRequirement.java` (lines 67-68)
108+
- Test: `server/java/metadata/src/test/java/com/metaobjects/registry/ChildRequirementCaseTest.java`
109+
110+
- [ ] **Step 1: Write the failing test**
111+
112+
```java
113+
package com.metaobjects.registry;
114+
115+
import org.junit.Test;
116+
import static org.junit.Assert.*;
117+
118+
public class ChildRequirementCaseTest {
119+
120+
@Test public void preserves_camelCase_expected_subtype() {
121+
ChildRequirement r = new ChildRequirement(
122+
"*", "source", "dbView", false, null, null, null, null, null);
123+
assertEquals("source", r.getExpectedType());
124+
assertEquals("dbView", r.getExpectedSubType()); // not "dbview"
125+
}
126+
127+
@Test public void null_becomes_wildcard() {
128+
ChildRequirement r = new ChildRequirement(
129+
"*", null, null, false, null, null, null, null, null);
130+
assertEquals("*", r.getExpectedType());
131+
assertEquals("*", r.getExpectedSubType());
132+
}
133+
}
134+
```
135+
136+
(Confirm the `ChildRequirement` constructor signature + the `getExpectedType()`/`getExpectedSubType()` accessor names by reading the class first; adjust the test to the real accessors. The contract: a camelCase `expectedSubType` is stored verbatim, and null still maps to `"*"`.)
137+
138+
- [ ] **Step 2: Run, verify it fails**
139+
140+
Run: `cd server/java && mvn -o -pl metadata test -Dtest=ChildRequirementCaseTest`
141+
Expected: `preserves_camelCase_expected_subtype` fails (`getExpectedSubType()` returns `"dbview"`).
142+
143+
- [ ] **Step 3: Remove the lowercasing**
144+
145+
In `ChildRequirement.java` (lines ~67-68), drop the `.toLowerCase()` (keep the null→`"*"` default):
146+
147+
```java
148+
this.expectedType = expectedType != null ? expectedType : "*";
149+
this.expectedSubType = expectedSubType != null ? expectedSubType : "*";
150+
```
151+
152+
(Update the adjacent `// Normalize types to lowercase…` comment to reflect that types are now stored verbatim / matched case-sensitively.)
153+
154+
- [ ] **Step 4: Run, verify pass**
155+
156+
Run: `cd server/java && mvn -o -pl metadata test -Dtest=ChildRequirementCaseTest`
157+
Expected: `Tests run: 2, Failures: 0, Errors: 0`.
158+
159+
- [ ] **Step 5: Commit**
160+
161+
```bash
162+
git add server/java/metadata/src/main/java/com/metaobjects/registry/ChildRequirement.java \
163+
server/java/metadata/src/test/java/com/metaobjects/registry/ChildRequirementCaseTest.java
164+
git commit -m "fix(metadata): ChildRequirement placement keys case-sensitive (no lowercasing)"
165+
```
166+
(Append the `Co-Authored-By` trailer.)
167+
168+
---
169+
170+
## Task 3: End-to-end gate — a camelCase subtype round-trips, wrong-case is rejected
171+
172+
Proves the full pipeline (register → parse → registry lookup → canonical serialize) preserves camelCase casing and now rejects non-canonical case. Since no camelCase subtype exists yet, register a **test-only** one, mirroring the existing Open-Closed proof test.
173+
174+
**Files:**
175+
- Test: `server/java/metadata/src/test/java/com/metaobjects/registry/CamelCaseSubtypeRoundTripTest.java`
176+
177+
- [ ] **Step 1: Read the Open-Closed proof harness**
178+
179+
Find the existing test that registers a one-off type via a class + a single `registry.registerType(...)` line and loads/serializes it (per ADR-0002 — search `server/java/metadata/src/test` for "Open-Closed", "fizz", or a registry-completeness/extensibility test). Reuse its registration + load + canonical-serialize harness. Confirm the canonical serializer entry point + how a test registers a transient type without polluting the shared registry.
180+
181+
- [ ] **Step 2: Write the failing test**
182+
183+
Behavioral contract (adapt to the real harness):
184+
185+
```java
186+
// CamelCaseSubtypeRoundTripTest
187+
// 1. Register a test field subtype with a CAMELCASE name, e.g. field.fizzBuzz, via the same
188+
// one-class + registerType pattern the Open-Closed proof test uses (a tiny MetaField subclass
189+
// with SUBTYPE = "fizzBuzz" and a registerTypes() that inheritsFrom field.base).
190+
// 2. Load metadata declaring it: { "metadata.root": { "package":"t::x", "children":[
191+
// { "object.pojo": { "name":"X", "children":[ { "field.fizzBuzz": { "name":"f" } } ] } } ] } }
192+
// (use object.pojo — entity/value arrive in WA2).
193+
// 3. Canonical-serialize the loaded root; assert the output contains the fused key "field.fizzBuzz"
194+
// (camelCase preserved end-to-end), NOT "field.fizzbuzz".
195+
// 4. Assert a wrong-case fixture { "field.fizzbuzz": {...} } now FAILS to load (unregistered type —
196+
// case-sensitive lookup rejects it): expect a load exception.
197+
```
198+
199+
Write the full JUnit4 test against the real harness; the assertions in (3) + (4) are the contract.
200+
201+
- [ ] **Step 3: Run, verify it passes** (Tasks 1+2 already make the pipeline case-sensitive)
202+
203+
Run: `cd server/java && mvn -o -pl metadata test -Dtest=CamelCaseSubtypeRoundTripTest`
204+
Expected: both assertions pass — `field.fizzBuzz` round-trips with case preserved; the wrong-case `field.fizzbuzz` fixture is rejected.
205+
(If (3) fails because a node-creation path still normalizes the subtype, trace it and fix the normalization at its source — do **not** weaken the assertion. If (4) does NOT reject — i.e. wrong-case still loads — the lookup isn't fully case-sensitive yet; find the remaining case-insensitive lookup and fix it.)
206+
207+
- [ ] **Step 4: Commit**
208+
209+
```bash
210+
git add server/java/metadata/src/test/java/com/metaobjects/registry/CamelCaseSubtypeRoundTripTest.java
211+
git commit -m "test(metadata): camelCase subtype round-trips end-to-end; wrong-case rejected (WA1 gate)"
212+
```
213+
(Append the `Co-Authored-By` trailer.)
214+
215+
---
216+
217+
## Task 4: Audit + reactor green
218+
219+
- [ ] **Step 1: Run the full metadata suite; triage breakages**
220+
221+
Run: `cd server/java && mvn -o -pl metadata test 2>&1 | grep -E "Tests run: [0-9]+, Fail|<<< (FAIL|ERROR)|BUILD" | grep -vE "Time elapsed" | tail -30`
222+
Expected baseline: the only pre-existing errors are the 2 known `CanonicalJsonParserTest` CWD-path NPEs (`corpusSpotCheck_loaderBasicEmptyPackage`, `corpusSpotCheck_smokeEmptyMetadata`). **Any other failure** is a case-sensitivity breakage from Tasks 1-2: a registration, fixture, or test that used a non-canonical-case type/subtype and relied on the old lowercasing.
223+
224+
- [ ] **Step 2: Fix each breakage to canonical casing**
225+
226+
For each new failure, the fix is to correct the offending type/subtype string to its **canonical** registered casing (e.g. a fixture `"field.String"``"field.string"`; a lookup `getType("Object","Base")``"object","base"`). Do **not** re-introduce lowercasing to paper over it — the canonical casing is the contract. Re-run the failing test after each fix.
227+
228+
- [ ] **Step 3: Downstream modules green (the registry change is module-wide)**
229+
230+
Run: `cd server/java && mvn -o install -pl core,metadata -DskipTests >/dev/null 2>&1 && mvn -o -pl omdb test 2>&1 | grep -E "Tests run:|BUILD" | tail -2 && mvn -o -pl core test 2>&1 | grep -E "Tests run:|BUILD" | tail -2 && mvn -o -pl maven-plugin test 2>&1 | grep -E "Tests run:|BUILD" | tail -2`
231+
Expected: `omdb`, `core`, `maven-plugin` BUILD SUCCESS (case-sensitivity must not regress them; fix any case-breakage the same way as Step 2).
232+
233+
- [ ] **Step 4: Final commit (if any audit fixes) + ready for review**
234+
235+
```bash
236+
git add -A && git commit -m "fix(metadata): audit — correct non-canonical type/subtype casing after case-sensitive registry"
237+
```
238+
(Append the `Co-Authored-By` trailer. Skip if Steps 1-3 found nothing to fix.)
239+
240+
---
241+
242+
## Self-Review
243+
244+
- **Spec coverage (WA1):** removes the lowercasing in `MetaDataTypeId` (Task 1) + `ChildRequirement` (Task 2) → case-sensitive on canonical vocabulary; the end-to-end gate (Task 3) proves camelCase round-trips + wrong-case is rejected; the audit (Task 4) corrects any non-canonical usage. The leave-list (auto-name prefixes, provider-id, field-subtype switch) is documented and untouched. ✓
245+
- **Accurate root-cause:** the lowercasing is in the lookup *key* + placement rules, not the node's stored subtype (which comes from the verbatim ctor args / canonical class constants) — so this is a case-*sensitivity* fix, not an output-mangling fix; framed as such. ✓
246+
- **Placeholder scan:** Tasks 1-2 carry full code + exact line targets; Task 3 is a behavioral contract + "read the Open-Closed proof harness first" (a real test that must be matched, per the accepted convention); Task 4 is an empirical audit with concrete commands + a concrete fix rule (correct to canonical casing). No TBDs. ✓
247+
- **Type consistency:** `MetaDataTypeId.type()/subType()/toQualifiedName()/matches()`; `ChildRequirement.getExpectedType()/getExpectedSubType()` (verify exact accessor names in Task 2). ✓
248+
- **Hygiene:** repo-relative paths; generic `t::x`/`source.dbView` examples; no home paths/private names. ✓

0 commit comments

Comments
 (0)