Skip to content

Commit 8faad2a

Browse files
dmealingclaude
andcommitted
Merge Java conformance follow-up: origin metatype + loader warnings infra
Issue A — port the origin metatype family to Java (MetaOrigin abstract + passthrough/aggregate/collection subtypes), accept origin as a child of field.*, ValidationPhase.validateOrigins enforces @agg vocabulary (ERR_BAD_ATTR_VALUE) and @via dotted-path traversal through declared relationships (ERR_INVALID_ORIGIN). Mirrors the TS/C# implementations. Closes 8 conformance ledger entries (origin-passthrough-simple, origin-aggregate-count, origin-aggregate-sum, origin-collection-simple, origin-multi-level-via, source-db-view-projection, error-origin-bad-aggregate-fn, error-origin-bad-via-path). Issue B — loader warnings surface on MetaDataLoader + missing-primary-identity warning emitted by ValidationPhase.validateEntityHasPrimaryIdentity. ConformanceTest honors expected-warnings.json via multiset-equality. Aligns with TS subtype-rules / C# ValidationPasses (no "has any field" guard — any concrete entity without primary identity warns). Closes subtype-entity-missing-primary-warning. Net: Java conformance ledger 39 → 30 (9 entries closed); ConformanceTest 168/0/0; metadata 614/0/0; full reactor green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2 parents 102850a + 9c220b3 commit 8faad2a

14 files changed

Lines changed: 1099 additions & 18 deletions

server/java/metadata/conformance-expected-failures.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
"error-extends-nonexistent",
1919
"error-field-object-storage-flattened-array",
2020
"error-field-object-storage-no-object-ref",
21-
"error-origin-bad-aggregate-fn",
22-
"error-origin-bad-via-path",
2321
"error-parse-malformed-json",
2422
"error-template-payload-ref-unresolved",
2523
"error-template-prompt-missing-payload-ref",
@@ -29,14 +27,7 @@
2927
"extends-cross-file",
3028
"layout-data-grid-basic",
3129
"layout-data-grid-multiple-named",
32-
"origin-aggregate-count",
33-
"origin-aggregate-sum",
34-
"origin-collection-simple",
35-
"origin-multi-level-via",
36-
"origin-passthrough-simple",
3730
"smoke-empty-metadata",
38-
"source-db-view-projection",
39-
"subtype-entity-missing-primary-warning",
4031
"template-output-and-prompt",
4132
"template-prompt-simple",
4233
"warning-filterable-no-index"

server/java/metadata/src/main/java/com/metaobjects/field/MetaField.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.metaobjects.constraint.PlacementConstraint;
1414
import com.metaobjects.constraint.RegexConstraint;
1515
import com.metaobjects.util.DataConverter;
16+
import com.metaobjects.origin.MetaOrigin;
1617
import com.metaobjects.validator.MetaValidator;
1718
import com.metaobjects.validator.MetaValidatorNotFoundException;
1819
import com.metaobjects.view.MetaView;
@@ -120,10 +121,11 @@ public static void registerTypes(MetaDataRegistry registry) {
120121
registry.registerType(MetaField.class, def -> {
121122
def.type(TYPE_FIELD).subType(SUBTYPE_BASE)
122123
.description("Base field metadata with common field attributes")
123-
// ACCEPTS ANY ATTRIBUTES, VALIDATORS AND VIEWS (all field types inherit these)
124+
// ACCEPTS ANY ATTRIBUTES, VALIDATORS, VIEWS, AND ORIGINS (all field types inherit these)
124125
.optionalChild(MetaAttribute.TYPE_ATTR, "*")
125126
.optionalChild(MetaValidator.TYPE_VALIDATOR, "*")
126-
.optionalChild(MetaView.TYPE_VIEW, "*");
127+
.optionalChild(MetaView.TYPE_VIEW, "*")
128+
.optionalChild(MetaOrigin.TYPE_ORIGIN, "*");
127129

128130
// FIELD-SPECIFIC ATTRIBUTES WITH FLUENT CONSTRAINTS
129131
def.optionalAttributeWithConstraints(ATTR_IS_ABSTRACT)

server/java/metadata/src/main/java/com/metaobjects/loader/MetaDataLoader.java

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.nio.charset.StandardCharsets;
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
31+
import java.util.Collections;
3132
import java.util.List;
3233
import java.util.Map;
3334
import java.util.concurrent.CompletableFuture;
@@ -111,6 +112,21 @@ public class MetaDataLoader implements LoaderConfigurable {
111112
// If set before init(), sources are loaded automatically during init().
112113
private List<URI> sourceURIs = null;
113114

115+
// Validation-phase warnings accumulator.
116+
//
117+
// Mirrors the TS/C#/Python warning surfaces (a per-load list of human-readable
118+
// warning strings produced by {@link ValidationPhase}). Consumers — primarily
119+
// the conformance harness today — read this after {@link #load(List)} returns
120+
// (which is the call site that runs {@link ValidationPhase#run(MetaRoot)}).
121+
//
122+
// Cleared at the start of every {@link #load(List)} so a subsequent load on
123+
// the same loader does not accumulate prior-batch warnings. Errors continue
124+
// to be eager-thrown — warnings are non-fatal, errors are not.
125+
//
126+
// Package-private mutator ({@link #addWarning(String)}) keeps callers
127+
// restricted to the loader package (where {@link ValidationPhase} lives).
128+
private final List<String> warnings = new ArrayList<>();
129+
114130
/**
115131
* Convenience constructor accepting only a name.
116132
* Uses {@link LoaderOptions} defaults (no-register, non-verbose, strict) and
@@ -245,6 +261,46 @@ public MetaDataLoader getLoader() {
245261
return this;
246262
}
247263

264+
///////////////////////////////////////////////////////////////////////
265+
// Validation warnings (cross-language warning surface)
266+
267+
/**
268+
* Returns the validation warnings produced by the most recent {@link #load(List)}
269+
* call (or accumulated across multiple loads in this batch). The list is reset
270+
* at the start of every {@link #load(List)} invocation.
271+
*
272+
* <p>Warnings are non-fatal advisory messages emitted by {@link ValidationPhase}.
273+
* Errors continue to be eager-thrown — only warnings accumulate here.</p>
274+
*
275+
* <p>Mirrors the TS/C#/Python warning surfaces; the canonical consumer is the
276+
* conformance harness comparing against {@code expected-warnings.json}.</p>
277+
*
278+
* @return an unmodifiable snapshot of the accumulated warnings (never {@code null})
279+
*/
280+
public List<String> getWarnings() {
281+
return Collections.unmodifiableList(new ArrayList<>(warnings));
282+
}
283+
284+
/**
285+
* Append a validation warning. Package-private — only the loader-package
286+
* validation passes ({@link ValidationPhase}) should be calling this.
287+
*
288+
* @param warning the warning message; ignored when {@code null} or empty
289+
*/
290+
void addWarning(String warning) {
291+
if (warning == null || warning.isEmpty()) return;
292+
warnings.add(warning);
293+
}
294+
295+
/**
296+
* Clear accumulated warnings. Called at the start of {@link #load(List)} so
297+
* a fresh batch does not see stale warnings from a prior load on the same
298+
* loader instance.
299+
*/
300+
void clearWarnings() {
301+
warnings.clear();
302+
}
303+
248304
///////////////////////////////////////////////////////////////////////
249305
// ClassLoader
250306

@@ -805,6 +861,10 @@ public boolean isRegistered() {
805861
public MetaDataLoader load(List<MetaDataSource> sources) {
806862
if (sources == null) throw new IllegalArgumentException("sources must not be null");
807863

864+
// Reset the per-load warning accumulator so callers see only warnings
865+
// produced by THIS batch.
866+
clearWarnings();
867+
808868
for (MetaDataSource source : sources) {
809869
String content;
810870
try {
@@ -830,8 +890,10 @@ public MetaDataLoader load(List<MetaDataSource> sources) {
830890

831891
// Run post-load validation passes after all sources in this batch are parsed.
832892
// Fires both when called from init() (via loadSourceURIsIfPresent) and when
833-
// called directly by tests or the conformance runner.
834-
ValidationPhase.run(root);
893+
// called directly by tests or the conformance runner. The loader handle is
894+
// passed so non-fatal validation findings can be recorded via
895+
// {@link #addWarning(String)} (errors continue to be eager-thrown).
896+
ValidationPhase.run(root, this);
835897

836898
return this;
837899
}

0 commit comments

Comments
 (0)