You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit abddef7
Browse filesBrowse the repository at this point in the historyBrowse files
fix(loader): the bare-object error names the ladder, not just the escape hatch
ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF fires at the exact moment an author
writes a shape they have not declared — and until now it answered with one
alternative, the open bag: "For an open/untyped JSON map use @dbColumnType:
jsonb on a field.string instead of a bare object." That is the last rung of a
three-rung ladder presented as the only rung, at the one moment the author is
most likely to take whatever the tool suggests. The authoring skill already
teaches the full ladder and says so explicitly ("the loader's pointer at
@dbColumnType: jsonb on that error is for the genuinely open case, not the
missing-VO case"); the loader itself did not.
All four ports now say the same thing, in each port's own quoting style:
a known key set → declare an object.value and @objectref it
dynamic keys, known value → field.map (@objectref or @valueType)
a bag no reader pins a key in → @dbColumnType: jsonb on a field.string
The rule and the code are unchanged — a bare field.object is still rejected,
with the same code and the same source envelope. Only the guidance changed.
expected-errors.json gates code + jsonPath, so no fixture churn; verified no
port test asserts the old substring (the two migrate-ts integration fixtures
that cited it in a COMMENT are corrected — both are legitimately on the last
rung, which is now said rather than implied).
docs/features/field-types.md listed 11 field subtypes where the registry
registers 17. The six missing rows (float, decimal, time, uri, inet, map) are
added with per-port native types read from each port's own mapper rather than
inferred, plus a footnote for the three rows that need one: the uri/inet
@lenient degrade, and field.map's real port coverage — Java's SpringTypeMapper
has no MapField arm, C# emits the property but DbContextGenerator writes no EF
storage mapping, and no persistence- or api-contract fixture exercises it on
any port.
Verified: TS metadata 2621/0; Python unit+conformance 947/0; C# MetaObjects
builds clean; Java metadata module compiles; scripts/ci-local.sh --quick
passed (leak-scan, fixture-lint, ts build+typecheck, conformance: typescript,
ts unit suites, doc-template drift, embedded-library drift all green).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
Copy file name to clipboardExpand all lines: fixtures/conformance/ERROR-CODES.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@
48
48
"ERR_BAD_ATTR_FILTER": "An attr.filter references a field that is not addressable in that context or uses a disallowed op: a dataGrid @filter over a non-filterable field or an op disallowed for the field's subtype, or a projection row-scope @filter over a non-declared (dangling) or aggregate-derived field (#207).",
49
49
"ERR_STORAGE_FLATTENED_ARRAY": "@storage \"flattened\" cannot be combined with isArray=true.",
50
50
"ERR_STORAGE_WITHOUT_OBJECT_REF": "@storage was set on a field that has no @objectRef.",
51
-
"ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF": "ADR-0013: a field.object declares no @objectRef. A field.object models a typed nested value and REQUIRES @objectRef. For a genuinely open/untyped JSON map, use the physical escape hatch @dbColumnType: jsonb on a field.string instead of a bare object.",
51
+
"ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF": "ADR-0013: a field.object declares no @objectRef. A field.object models a typed nested value and REQUIRES @objectRef. The message states the whole ladder rather than only the escape hatch: a known key set is an object.value the field @objectRefs; dynamic keys over a known value type are a field.map (@objectRef for a value object, @valueType for a scalar); only a bag no reader pins a key in is @dbColumnType: jsonb on a field.string. Naming the escape hatch alone is what taught adopters to reach for the bag by default.",
52
52
"ERR_UNRESOLVED_OBJECT_REF": "ADR-0042: a field.object / field.map @objectRef does not resolve to any object in the loaded tree (a dangling target). The ref resolves package-locally when bare (referrer's package, else root-level) and exactly when FQN — a bare cross-package ref no longer binds elsewhere. The error names same-short-name objects in other packages so the author can qualify it.",
53
53
"ERR_RESERVED_ATTR": "An @-prefixed reserved structural keyword (e.g. @name, @isArray, @children) was used as an inline attribute.",
54
54
"ERR_SCOPE_PATTERN_INVALID": "Phase-1 metadata-source-resolution: a scope include/exclude package pattern is malformed (empty pattern or empty :: segment).",
Copy file name to clipboardExpand all lines: server/typescript/packages/metadata/src/loader/validation-passes.ts
+8-3Lines changed: 8 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -1672,8 +1672,13 @@ export function validateDerivedFieldProvidability(root: MetaData): ParseError[]
1672
1672
// Rules (ADR-0013):
1673
1673
// 1. A field.object ALWAYS requires @objectRef. A field.object models a typed
1674
1674
// nested value; without @objectRef it is "an oxymoron at the logical layer".
1675
-
// Genuinely open/untyped JSON uses the physical @dbColumnType: jsonb escape
1676
-
// hatch on field.string, NOT a bare object. → ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF.
1675
+
// → ERR_OBJECT_FIELD_WITHOUT_OBJECT_REF. The MESSAGE states the whole ladder,
1676
+
// not just the last rung: a known key set is an object.value the field
1677
+
// @objectRefs; dynamic keys over a known value type are a field.map; only a
1678
+
// bag no reader pins a key in is @dbColumnType: jsonb on a field.string.
1679
+
// Naming the escape hatch alone — which this error did until now — is what
1680
+
// taught adopters to reach for the bag by default, and it is the mechanism
1681
+
// behind every untyped-jsonb column an adoption audit finds.
1677
1682
// (This rule subsumes the legacy @storage-without-@objectRef check —
1678
1683
// @storage is only meaningful on a field.object, so the missing-@objectRef
1679
1684
// situation now always reports this single, clearer error. One error per
@@ -1699,7 +1704,7 @@ export function validateFieldObjectStorage(root: MetaData): ParseError[] {
1699
1704
// further @storage error on the same node would be redundant.
1700
1705
errors.push(
1701
1706
newParseError(
1702
-
`field.object "${obj.name}.${field.name}" has no @objectRef; a field.object requires @objectRef. For an open/untyped JSON map use @dbColumnType: jsonb on a field.string instead of a bare object.`,
1707
+
`field.object "${obj.name}.${field.name}" has no @objectRef; a field.object models a typed nested value and requires one. Take the first of these that fits: a known key set → declare an object.value and point @objectRef at it; dynamic keys over a known value type → field.map with @objectRef (a value object) or @valueType (a scalar); a genuinely open bag no reader pins a key in → @dbColumnType: jsonb on a field.string.`,
0 commit comments