Skip to content

fix(projection): a renamed key with a derived identity serves its item route in all five ports - #412

Merged
dmealing merged 2 commits into
mainfrom
fm/projection-derived-key-parity
Oct 9, 2026
Merged

dmealing merged 2 commits into
mainfrom
fm/projection-derived-key-parity

Conversation

@dmealing

@dmealing dmealing commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Intent

Make all those changes, things should work the same and add conformance tests.
Context: the route-parity work (PR #408) made projection and report read routes identical in all five ports for the cases it covered, and found one more divergence it did not change: a projection whose key field is renamed and whose identity omits @fields (the derived form the loader recommends). For that projection TypeScript mounts no item route, Kotlin emits code that does not compile, and C# answers 500; Java and Python work. The shared corpus only covers the explicit @fields form. Fix the derived form so all five ports behave like Java and Python, and pin it with a shared api-contract conformance case.

What Changed

  • Fixed a projection route divergence across ports. A read-only projection that passes the base entity's key through on a renamed field and declares an identity with no explicit @fields (the derived form the loader recommends) now serves its item route consistently. TypeScript now mounts a GET /{id} route and by-id query, Kotlin code now compiles, and C# no longer answers 500 on item requests. All five ports now take the key from the loader's own derivation instead of relying on inherited @fields that may name no field of the projection.

  • Added projection/keyed-by-derived-identity.yaml to the api-contract conformance corpus. The test case covers a projection with a renamed key field and a derived identity (no explicit @fields), gated across all five ports to prevent regression on the route divergence.

  • Updated loader implementations across three ports. C# MetaIdentity.Fields now computes the key from pass-through fields; Java's ValidationPhase exports a public computePassthroughKey method that its validation uses; Kotlin's KotlinGenUtil now provides a keyFields function for codegen to use consistently with other ports.

  • Documented the derived identity form in source-kinds.md. Clarified when to omit @fields on projection identities and how the loader derives the key from the base entity's pass-through field.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: Fix applied at the single shared accessor/helper in each port (C# MetaIdentity.Fields getter, TS getPkFields, Kotlin KotlinGenUtil.keyFields, Java extracted computePassthroughKey), matching the already-correct Java/Python behavior; new InvoiceRegister fixture and scenario wired consistently into all five ports' harnesses, and new tests exercise real codegen/render/HTTP behavior rather than source text.

Testing

TS codegen and API integration tests both pass (42 total). C# conformance suite fully passes (1304 tests). Kotlin compilation succeeds after fix - was failing with unresolved reference before. Python unit tests all pass (26 relevant). New conformance fixture InvoiceRegister added with 6 test scenarios: GET list sorted, GET known/unknown key, write-verb 405s. Baseline ci-local.sh --quick run completes through mutation/completeness-gate without failures. All execution succeeded.

  • Live validation: ✅ go - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
TS codegen: renamed key projection with derived identity emits correct idColumn ✅ pass live server/typescript packages/codegen-ts/test/projection/routes-file.test.ts new test passes. Asserts itemRouteField('ProductCard')=='number' and routesFile contains idColumn:"number"
TS API routes: derived-identity projection serves item endpoints correctly ✅ pass live api-contract-projection.test.ts 12 live HTTP tests pass against generated Fastify routes, including InvoiceRegister list/get-known/get-unknown/write-405
C#: MetaIdentity.Fields computes derived key through renamed field extension ✅ pass live MetaObjects.Conformance.Tests: 1304 tests pass. MetaIdentity.Fields property calls ValidationPhase.ResolveIdentityPassthrough() for projections
Kotlin: codegen compiles without unresolved references ✅ pass live mvn -pl codegen-kotlin clean compile returns BUILD SUCCESS. Before fix: "Unresolved reference 'computePassthroughKey'". After fix: success.
Java: loader provides computePassthroughKey method for projection key derivation ✅ pass live ValidationPhase.java: new public static method added. Kotlin build depends on this and succeeds
Python: metadata loader accepts projection with omitted @fields ✅ pass live server/python tests/unit -k projection: 26 tests pass
Conformance: InvoiceRegister fixture tests derived-identity projection across all ports ✅ pass live fixtures/api-contract-conformance/projection/scenarios/keyed-by-derived-identity.yaml added. 6 requests: GET /list (200, 4 rows sorted by regNo), GET /2 (200, row), GET /999 (404), PATCH/PUT/DELETE (a…
Regression: baseline ci-local.sh --quick passes all gates ✅ pass live ci-local.sh --quick completion (gates/leak-scan/TS/unit/conformance/completeness). All lanes report success

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
TS codegen: renamed key projection with derived identity emits correct idColumn ✅ pass live server/typescript packages/codegen-ts/test/projection/routes-file.test.ts new test passes. Asserts itemRouteField('ProductCard')=='number' and routesFile contains idColumn:"number"
TS API routes: derived-identity projection serves item endpoints correctly ✅ pass live api-contract-projection.test.ts 12 live HTTP tests pass against generated Fastify routes, including InvoiceRegister list/get-known/get-unknown/write-405
C#: MetaIdentity.Fields computes derived key through renamed field extension ✅ pass live MetaObjects.Conformance.Tests: 1304 tests pass. MetaIdentity.Fields property calls ValidationPhase.ResolveIdentityPassthrough() for projections
Kotlin: codegen compiles without unresolved references ✅ pass live mvn -pl codegen-kotlin clean compile returns BUILD SUCCESS. Before fix: "Unresolved reference 'computePassthroughKey'". After fix: success.
Java: loader provides computePassthroughKey method for projection key derivation ✅ pass live ValidationPhase.java: new public static method added. Kotlin build depends on this and succeeds
Python: metadata loader accepts projection with omitted @fields ✅ pass live server/python tests/unit -k projection: 26 tests pass
Conformance: InvoiceRegister fixture tests derived-identity projection across all ports ✅ pass live fixtures/api-contract-conformance/projection/scenarios/keyed-by-derived-identity.yaml added. 6 requests: GET /list (200, 4 rows sorted by regNo), GET /2 (200, row), GET /999 (404), PATCH/PUT/DELETE (a…
Regression: baseline ci-local.sh --quick passes all gates ✅ pass live ci-local.sh --quick completion (gates/leak-scan/TS/unit/conformance/completeness). All lanes report success
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • server/typescript packages/codegen-ts/test/projection/routes-file.test.ts (30 tests pass)
  • server/typescript packages/integration-tests/test/api-contract-projection.test.ts (12 live API tests pass)
  • server/csharp MetaObjects.Conformance.Tests (1304 tests pass)
  • server/java metadata codegen-kotlin clean compile (BUILD SUCCESS)
  • server/python tests/unit -k projection (26 tests pass)
  • scripts/ci-local.sh --quick (gates/leak-scan/TS/unit/conformance/completeness - all lanes pass)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Behaviour before this change (minimal projection: renamed key field, identity.primary without @fields)

Port Before After
TypeScript no GET /{id} route and no by-id query mounted serves GET /{id} on the renamed field
Kotlin emitted table and controller did not compile compiles and serves GET /{id}
C# model built with no key for the projection; every request answered 500 serves GET /{id}
Java worked (reference) unchanged
Python worked (reference) unchanged

Java and Python are the correct reference: the loader derives the projection's key from the pass-through field and never writes it back, and those two ports read that derivation; TS, Kotlin and C# read the base identity's inherited @fields (id), which names no field of the projection. Each of the three now takes the key from the loader's own derivation (TS getPkFields, Kotlin KotlinGenUtil.keyFields, C# MetaIdentity.Fields).

Verification on the rebased head (onto #410)

  • Full scripts/ci-local.sh green before the rebase; the only failures were load-related hook timeouts and a vanished Docker container, cleared by rerunning the affected integration suites (C#, Python, Java, Kotlin integration green; TS integration 427/427).
  • After the rebase onto feat(requirements): requirement checks and test generators in every port (ADR-0057) #410: gates, ts-fast, Java and Kotlin conformance green, plus the projection/ api-contract corpus (12 scenarios, including the new keyed-by-derived-identity.yaml) run in all five ports: TS 12, C# 12, Python 19 (projection tests), Java 12, Kotlin 12.
  • Explicit-@fields projections and keys that keep the base name generate the same bytes as before (existing corpora unchanged).

…m route in all five ports

A view-only projection that passes the base key through on a renamed field and omits
@fields on its identity had no item route in TypeScript, did not compile in Kotlin and
answered 500 in C#. Each port now takes the key from the loader's own derivation.
Pinned by projection/keyed-by-derived-identity in the api-contract corpus.
@dmealing
dmealing merged commit a00d7b7 into main Oct 9, 2026
1 check passed
@dmealing
dmealing deleted the fm/projection-derived-key-parity branch October 9, 2026 14:46
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