feat(metamodel): origin.passthrough is type-preserving unless @convert#186
Merged
Conversation
#185) A field carrying `origin.passthrough @from: "Entity.field"` forwards the source value unchanged, so its declared `field.<subType>` and array-ness must be identical to the resolved source field. A divergence now fails load with `ERR_PASSTHROUGH_TYPE_MISMATCH` — catching the mismodeling where a `field.uuid` source is surfaced by a projection/view as `field.string` (which silently leaves the field String-typed end-to-end and forces hand-written String<->UUID bridging, defeating a UUID migration). `verify --db` can't see this — the column IS uuid; only the metadata model knows both ends of the passthrough. The check compares subType + array-ness only — nullability is deliberately NOT judged (a view over an outer join legitimately widens NOT NULL -> nullable, so a nullability check would false-positive on valid projections). Escape hatch: a new optional boolean attr `@convert: true` on `origin.passthrough` acknowledges a deliberate type change and suppresses the error. It is an acknowledgement ONLY — it does not generate a cast; the consumer owns any coercion. Real type-converting projections remain origin.expression's job (#159), so @convert is NOT blocked on that unbuilt feature. This GENERALIZES and RETIRES the narrow, stored-proc-parameter-ref-only `ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH` (FR-015) into one host-agnostic invariant covering projections, entities, values, and parameter refs alike, enforced once in the origin-paths validation pass. Cross-port: implemented in all five ports (TS reference + Python / C# / Java; Kotlin inherits Java's loader), each registering @convert on origin.passthrough and byte-matching the shared registry-conformance manifest. Gated by three new conformance fixtures (type-mismatch, array-mismatch, @convert opt-out) plus the repointed parameter-ref fixture. BREAKING: adopter metadata with a divergent passthrough now fails load (the point — it names both types + the fix). Also fixes a pre-existing generator gap: the `index` metamodel concept was missing from generate-embedded-metamodel.ts's CONCEPT_DIRS. Co-Authored-By: Claude <noreply@anthropic.com>
dmealing
added a commit
that referenced
this pull request
Jul 9, 2026
….8 (Maven) Coordinated release across all four registries. Three merged efforts: - #185/#186 — BREAKING: origin.passthrough is type-preserving (+@convert) - #187 — typed value-object jsonb columns all ports (Kotlin→Jackson; C#/Java/Python array-of-VO) - #188/#189 — load-order-independent super-resolution (Node-vs-Bun build portability) npm: 14-package lockstep 0.15.17. PyPI metaobjects 0.15.10. NuGet MetaObjects* 0.15.8. Maven com.metaobjects:* 7.7.8. See CHANGELOG.md [0.15.17]. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Implement GitHub issue #185: make origin.passthrough type-preserving across all five language ports. A projection/view field forwarding another field's value via origin.passthrough @from must declare the same field. and array-ness as its resolved source field; a divergence fails load with a new ERR_PASSTHROUGH_TYPE_MISMATCH. Catches the mismodeling where a field.uuid source is surfaced as field.string on a view (leaving it String-typed end-to-end, forcing hand-written String<->UUID coercion) — verify --db can't see it (the column really is uuid); only the metadata knows both ends.
KEY DELIBERATE DECISIONS (not mistakes — treat as intended):
Fixtures-first TDD: 3 new conformance fixtures as the cross-port oracle. Ported to Python/C#/Java (Kotlin shares Java's loader) — all green (TS 459, Python 340, Java 438, C# 718). 4-angle code-simplifier pass applied and re-verified. BREAKING metamodel change: adopter metadata with a divergent passthrough now fails load — intended. CHANGELOG breaking note + agent-context authoring skill + regenerated goldens/registry/metamodel-docs all included.
What Changed
origin.passthroughis now type-preserving across all five ports (TS/C#/Java/Python; Kotlin shares Java's loader): a projection/view field forwarding another field's value via@frommust declare the samefield.<subType>and array-ness as its resolved source. A divergence (e.g. afield.uuidsource surfaced asfield.stringon a view) now fails load with the new generalizedERR_PASSTHROUGH_TYPE_MISMATCH, raised in the origin-paths validation pass that runs over every object. SubType + array-ness are compared only — nullability is deliberately excluded so a legitimate NOT NULL → nullable outer-join widening does not false-positive.ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH(stored-proc@parameterRefonly) into the single host-agnosticERR_PASSTHROUGH_TYPE_MISMATCH; the old narrow-check blocks are removed and the parameter-ref fixture is repointed to the generalized code. Adds a new boolean@convertattr onorigin.passthroughas an acknowledgement-only escape hatch that suppresses the error (consumer owns coercion; no cast is generated), registered as a first-class provider attr in all five ports and byte-matched to the sharedregistry-conformancemanifest.indexconcept missing fromgenerate-embedded-metamodel.tsCONCEPT_DIRS). This is a breaking metamodel change: adopter metadata with a divergent passthrough now fails to load.Risk Assessment
✅ Low: A well-bounded, cross-port-conformance-gated loader change with consistent TS/C#/Java/Python implementations, correct ADR-0039 own-vs-resolving accessor usage, full coverage preservation of the retired narrow check, and a documented @convert escape hatch; the breaking metamodel behavior is intentional and well-justified.
Testing
Installed TS deps (bun install), Python dev extras (uv sync), and built the C#/Java ports; ran the cross-port conformance oracles which are the test of record for the #185 fixtures, plus the touched TS unit tests (fr015, origin-completeness, registry). All green across TS, C#, Java (Kotlin shares its loader), and Python. To produce reviewer-visible end-to-end evidence, I drove the real TS loader directly against the three #185 conformance fixture inputs (same provider composition the conformance runner uses) and captured the actual user-facing behavior: a field.uuid source surfaced as field.string on a projection view fires ERR_PASSTHROUGH_TYPE_MISMATCH with the message telling the author to either declare field.uuid or set @convert:true; a string[]→string array-ness divergence fires the same code; and @convert:true loads clean with zero errors. The transient evidence script was removed from the working tree after capturing the transcript; the tree is clean and evidence lives only in the dedicated evidence dir. No setup issues, flakiness, or failures encountered.
Evidence: TS loader transcript — #185 type-preservation end-to-end (ERR_PASSTHROUGH_TYPE_MISMATCH on subType + array-ness divergence; @convert loads clean; full human-facing error message)
=== error-origin-passthrough-type-mismatch === errorCodes: ["ERR_PASSTHROUGH_TYPE_MISMATCH"] - code=ERR_PASSTHROUGH_TYPE_MISMATCH referrer=CharacterView::currentLocationId target=demo::GameCharacter.currentLocationId === error-origin-passthrough-array-mismatch === errorCodes: ["ERR_PASSTHROUGH_TYPE_MISMATCH"] - code=ERR_PASSTHROUGH_TYPE_MISMATCH referrer=ProductView::tags target=demo::Product.tags === origin-passthrough-convert-optout === errorCodes: [] (loaded clean) === Human-facing loader message (subType mismatch) === origin.passthrough on CharacterView.currentLocationId: field is field.string but its @from source "demo::GameCharacter.currentLocationId" is field.uuid — a passthrough forwards the value unchanged, so the types must match. Declare field.uuid, or set @convert: true to acknowledge a deliberate type change.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.
bun test packages/metadata (TS metadata package — 2119 pass, includes origin/validation-passes)bun test packages/metadata/test/conformance.test.ts -t passthrough (16 passthrough conformance scenarios — the 3 new #185 fixtures + repointed parameter-ref fixture + existing passthrough fixtures)bun test packages/metadata/test/fr015-source-parameter-ref.test.ts packages/metadata/test/origin-definition-completeness.test.ts packages/metadata/test/registry-conformance.test.ts (31 pass — touched by the diff)dotnet test MetaObjects.Conformance.Tests (C# — 718 pass, includes new passthrough fixtures)mvn -o test -Dtest=ConformanceTest (Java metadata — 428 pass) + -Dtest=RegistryManifestConformanceTest (3 pass — @convert registration)uv run pytest tests/conformance/test_conformance.py tests/conformance/test_registry_conformance.py (Python — 221 pass)bun run transient evidence script driving the real TS loader against the 3 #185 fixtures — captured ERR_PASSTHROUGH_TYPE_MISMATCH firing on subType + array-ness mismatch and clean load with @convert, plus the full human-facing ParseError messagedocs/superpowers/specs/2026-05-28-fr-015-source-parameter-ref-design.md:106- The historical FR-015 design spec (and two sibling plan/spec docs) still name the retired ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH error code and describe the pre-generalization parameter-ref-only type-match check. The living reference docs (ERROR-CODES.json, metamodel-docs, migration guide, cross-language porting guide) now all reflect the generalized ERR_PASSTHROUGH_TYPE_MISMATCH, but these design journals were left as-is.server/python/src/metaobjects/loader/validation_passes.py:1522- New_check_passthrough_typecallsresolved_source(origin_source, ...)whereorigin_source: objectbutresolved_sourceexpectsErrorSource— mypy strict reportsArgument 1 ... incompatible type 'object'; expected 'ErrorSource'. This is one new mypy error vs. the 53-error baseline; it copies the identical pre-existing pattern already present 5× in this file (e.g. line 1475 in_check_extends_origin_agreement), originating fromMetaOrigin.sourcebeing typedobject. mypy is configured (strict) but NOT a CI gate (Python CI runs pytest only), so the codebase tolerates these. Fixing properly requires a cross-cuttingMetaOrigin.sourcetyping change, out of scope for this change.✅ **Push** - passed
✅ No issues found.