Repository navigation
feat(metadata): walk identity.reference backwards in origin @via hops - #560
Merged
Merged
Conversation
…hop An origin @via hop may now walk an identity.reference backwards, from the entity it targets to the entity holding it, named by the holder's collection name in lower camel case (@collectionName when declared, else the project's pluralizer). The hop is to-many, and two references answering one name are refused with ERR_AMBIGUOUS_PATH; a declared relationship or reference of the same name still wins. TypeScript, C#, Java (Kotlin through it) and Python implement it, gated by the shared fixtures origin-reverse-reference-hop, origin-reverse-hop-collection-name and error-origin-reverse-hop-ambiguous.
dmealing
added a commit
that referenced
this pull request
Nov 12, 2026
…ct aggregates (#561) * docs(agent-context): route report wording to object.report before @SQL The meta init agent guide gains one rule: a grouped total, dashboard number or read-only report view is an object.report over dimension.* / measure.*, with a hand-written @SQL view body as the last resort. The metaobjects-authoring routing table sends report, dashboard, totals, GROUP BY and per-month wording to declaring-reports.md first, and the @SQL sections of read-views-and-projections.md and sources.md point an aggregating body at it and say what @SQL is for. * fix(codegen-ts): emit view columns fed by a reverse identity.reference @via hop An origin @via hop that walks an identity.reference backwards (#560) loaded, but the projection view emitter resolved hops only through relationships and forward references and silently dropped the aggregate and first columns from CREATE VIEW. It now falls back to the same reverse-hop lookup the loader uses and joins from the reference holder. Gated by a Postgres test that migrates, inserts and reads the view. * fix(metadata): refuse origin.aggregate sum/avg/min/max over a value-object field @Of naming a field.object (flattened or jsonb) loaded and emitted SUM(<field>), a column that does not exist. All four loaders (Kotlin through Java) now refuse it with ERR_INVALID_ORIGIN: a value object is several columns or a document, and summing a Money-shaped value would add amounts in different currencies. count is unaffected. Gated by the new error-origin-aggregate-value-object conformance fixture. * chore: declare non-production paths for no-mistakes triage Fixtures, the metamodel spec and its generated per-port copies, the generated reference site and the showcase payload. * fix(java): guard a missing @agg before the value-object aggregate check List.of(...).contains(null) throws, so an origin.aggregate with no @agg over a resolvable @Of crashed the loader instead of reporting the missing attribute. * no-mistakes(document): regenerate showcase payload fixture count
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.
What Changed
@viahop can now walk anidentity.referencebackwards, from the referenced entity to the entity that declares it, named by the holder's collection name in lowerCamel (e.g.Patient.consentswalksConsent.patient). A declared relationship or reference of the same name still wins. The loader refuses two candidate holders withERR_AMBIGUOUS_PATH. The reverse hop is to-many: it is valid in an aggregate and rejected in a passthrough withERR_ORIGIN_CARDINALITY. Implemented in the TypeScript, C#, Java and Python loaders, with the new helpersfindReverseReferenceHops/reverseHopNameexported from@metaobjectsdev/metadatain TypeScript.CollectionNames) and Python (collection_names.py), matching the existing TypeScript and C# behavior: the object's own@collectionName, else the configured pluralizer.origin-reverse-reference-hop,origin-reverse-hop-collection-name,error-origin-reverse-hop-ambiguous), and updatesCHANGELOG.md,docs/features/relationships.md,docs/features/migrations/physical-naming.md,docs/CONFORMANCE.md,AGENTS.mdandexamples/showcase/site-payload.jsonfor the new fixture count (455 to 458).@filter(7), and flattened-member paths, two references onto one target and the report "required" rule (4). feat(metadata): add @collectionName and naming.collections pluralizer across all five ports #558 landed@collectionNameand thenaming.collectionspluralizer.Validation
Risk Assessment
✅ Low: Reverse @via hop logic is consistent across all four loader implementations and the three fixtures gate it. The prior fix round's doc, message and Java isAbstract changes check out; only comment-level nits remain.
Testing
Live: reverse-hop origin @via feature driven through the TS CLI and the Python loader on disposable project copies. Ambiguous reverse hop gives ERR_AMBIGUOUS_PATH; reverse-hop passthrough gives the new reverse-hop message; declared-reference and collection-name fixtures load clean. Not live: Python 6-case conformance subset, Java metadata module, C# conformance, and TS/Java broad regression runs were unit/conformance harness runs only and are recorded as untested-live. Python full unit suite did not finish (killed); loader and conformance dirs pass. Integration tests need pg8000 and Postgres and were not run. Verdict go on the live-driven scenarios; Python full unit suite and the 21-vs-3 fixture count question remain open.
error-origin-reverse-hop-ambiguous(synthetic copy of fixture input)origin-reverse-reference-hoporigin-reverse-hop-collection-namepassthrough-reverse(synthetic edit of fixture input)Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
docs/features/relationships.md:176- The @via section still states 'Inverse navigation (parent → many children) still needs a relationship.composition on the parent — a bare FK has no inverse edge.' This change makes that false: an origin @via hop may now walk an identity.reference backwards, named by the holder's collection name in lowerCamel (Patient.consents), to-many, ERR_AMBIGUOUS_PATH on two candidates, declared relationship/reference wins. The CHANGELOG and physical-naming.md were updated but the primary feature doc for @via was not, so it now contradicts loader behaviour in all five ports. Update lines 176-178 to describe the reverse hop (and that single-hop-unique inference still stays relationship-only). Not in the changed-file list, so not fixed inline.server/typescript/packages/metadata/src/loader/validation-passes.ts:1065- When a passthrough crosses a reverse hop, ERR_ORIGIN_CARDINALITY says '@via hop "patient" is to-many (@Cardinality "many")'. It names the walked reference ("patient"), not the path segment the author wrote ("consents"), and claims a declared @Cardinality that does not exist. Same message in every port: server/csharp/MetaObjects/Loader/ValidationPasses.cs:1368, server/java/metadata/src/main/java/com/metaobjects/loader/ValidationPhase.java:4923, server/python/src/metaobjects/loader/validation_passes.py:1993. Consider saying 'reverse hop over identity.reference "patient"' for hops in the reverse set. The message text must stay identical across ports.server/java/metadata/src/main/java/com/metaobjects/loader/ReverseReferenceHops.java:55- ReverseReferenceHops.isAbstract is a verbatim copy of the private ValidationPhase.isAbstract (ValidationPhase.java:4043), in the same package. Make the ValidationPhase helper package-private and reuse it, so there is one own-only @isAbstract rule.🔧 Fix applied.
3 infos still open:
server/java/metadata/src/main/java/com/metaobjects/loader/ValidationPhase.java:4591- Inline fix: the new Java helperrootObjectshad been inserted between the Javadoc ofresolveRootObjectand its method, detaching that doc. Moved the helper above the Javadoc.server/typescript/packages/metadata/src/core/relationship/find-reference.ts:75- Inline fix: reworded the garbledreverseHopNameJavadoc (it had a duplicated, half-templated phrase).fixtures/conformance/origin-reverse-reference-hop/expected.json- No fixture asserts the passthrough-across-reverse-hopERR_ORIGIN_CARDINALITYmessage, so the byte-identical wording across ports is unverified. The three new fixtures cover only the happy path, a collection-name override and the ambiguous case.error-origin-reverse-hop-ambiguous(synthetic copy of fixture input)origin-reverse-reference-hoporigin-reverse-hop-collection-namepassthrough-reverse(synthetic edit of fixture input)scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsbun test in server/typescript/packages/metadata (conformance.test.ts, conformance-twin-roundtrip.test.ts, then full package)live: bun server/typescript/packages/cli/dist/bin/meta.js verify on disposable copies of error-origin-reverse-hop-ambiguous, origin-reverse-reference-hop, origin-reverse-hop-collection-namelive: same CLI on a disposable passthrough-over-reverse-hop project (origin.passthrough variant of origin-reverse-reference-hop)PYTHONPATH=server/python/src python3 -m pytest tests/conformance/test_conformance.py -k reverse|collection (6 new fixture cases pass)live: python loader via tests/conformance/conformance_adapter.load_fixture on disposable copies of the 3 fixturesPYTHONPATH=src python3 -m pytest tests/loader tests/conformance (1608 pass)python -m pytest --ignore=tests/integration (full unit run; stalled and was killed, no result)mvn -o -q -pl metadata test in server/java (3349 run, 0 fail, 0 err)dotnet restore + dotnet test MetaObjects.Conformance.Tests --no-restore (2414 passed, 0 failed)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.