Diagnostics
The Henceforth compiler emits diagnostics during metadata compilation. Diagnostics surface modeling feedback, configuration warnings, and errors alongside the compiled schema. They are returned in the response body of both /plan and /apply API calls.
Diagnostics are not limited to fatal errors. They serve as architecture-level linting – alerting modelers to valid-but-questionable configurations, auto-corrections, and potential issues before they reach the database.
Diagnostic Shape
Section titled “Diagnostic Shape”Each diagnostic contains three fields:
| Field | Type | Description |
|---|---|---|
severity |
string | The severity level. See Severity Levels. |
message |
string | A human-readable description of the issue. |
source |
string | The metadata location that triggered the diagnostic, in {module}_{object}.{field} format (e.g., core_Contact.account). |
Severity Levels
Section titled “Severity Levels”| Level | Meaning | Effect on compilation |
|---|---|---|
info |
Informational. No action needed. | Compilation proceeds. |
warning |
Potential issue. Review recommended. The compiler may have auto-corrected the configuration. | Compilation proceeds. |
error |
Invalid configuration. Must be fixed. | Compilation proceeds but the result may be incomplete. |
blocking |
Cannot compile. Apply refused. | Compilation halts. /apply does not execute DDL. |
Diagnostic Catalog
Section titled “Diagnostic Catalog”Required lookup with setNull
Section titled “Required lookup with setNull”- Severity:
warning - Trigger: A lookup field specifies
required: trueandonParentDelete: "setNull". This is contradictory – aNOT NULLcolumn cannot be set to null on parent delete. - Compiler action: Auto-corrects
onParentDeletetorestrict. - Message:
"required: true with onParentDelete: setNull is invalid — auto-corrected to restrict" - Source:
{module}_{object}.{field}(e.g.,core_Contact.account)
Subtype widening of existing lookups
Section titled “Subtype widening of existing lookups”- Severity:
info - Trigger: A new object extends a base type, and one or more existing lookup fields target that base type. The new subtype will be accepted by those lookups through the FK to the base table.
- Compiler action: None. Informational only.
- Message:
"New type '{module}.{subtype}' extends '{base}' — will be accepted by lookup '{lookupObject}.{lookupField}'" - Source:
{module}_{lookupObject}.{lookupField}
This diagnostic helps modelers understand the impact of adding a new subtype to an inheritance hierarchy. If the widening is unintended, the modeler can switch the lookup from to: "core.Party" to an explicit multi-target to: ["core.Individual", "core.Organization"].
Multi-target common ancestor
Section titled “Multi-target common ancestor”- Severity:
info - Trigger: All targets in a multi-target lookup (
to: [...]) are subtypes of a common ancestor, and the listed targets are exactly all current subtypes of that ancestor. - Compiler action: None. Informational only.
- Message: Suggests using
to: "{ancestor}"for tighter FK integrity, while noting that this also accepts future subtypes. - Source:
{module}_{object}.{field}
When the targets share a common ancestor but are not an exact match to all current subtypes, the compiler emits a warning instead, since the modeler may be intentionally restricting the allowed types.
Circular inheritance
Section titled “Circular inheritance”- Severity:
blocking - Trigger: The
extendsgraph contains a cycle (e.g., A extends B, B extends A). - Compiler action: Compilation halts. No DDL is generated.
- Message:
"Circular inheritance detected: {object1} -> {object2} -> ..."
Rollup sourceField ignored for count
Section titled “Rollup sourceField ignored for count”- Severity:
warning - Trigger: A rollup field with
function: "count"specifies asourceField. ThesourceFieldis not used for count aggregation. - Compiler action: The
sourceFieldis ignored. - Message:
"sourceField is ignored for count rollups" - Source:
{qualifiedName}.{fieldKey}
Invalid extends target
Section titled “Invalid extends target”- Severity:
blocking - Trigger: An object’s
extendsproperty references a qualified name that does not exist in the metadata set, or uses an invalid format. - Compiler action: Compilation halts.
- Message:
"{object}: extends target '{target}' — object not found"
Invalid lookup target
Section titled “Invalid lookup target”- Severity:
blocking - Trigger: A lookup field’s
toproperty references an object that does not exist in the metadata set. - Compiler action: Compilation halts.
- Message:
"{source}: lookup target '{target}' — object not found"
Invalid rollup relationship
Section titled “Invalid rollup relationship”- Severity:
blocking - Trigger: A rollup field’s
relationshipdoes not match any child lookup field’stoInverseKeytargeting this object. - Compiler action: Compilation halts.
- Message:
"{source}: rollup relationship '{relationship}' — no child lookup found targeting '{object}' with toInverseKey '{relationship}'"
Unknown rollup function
Section titled “Unknown rollup function”- Severity:
blocking - Trigger: A rollup field’s
functionis not one of the supported aggregation functions (count,sum,min,max,avg). - Compiler action: Compilation halts.
- Message:
"{source}: unknown rollup function '{function}' — expected one of: count, sum, min, max, avg"
Missing rollup sourceField
Section titled “Missing rollup sourceField”- Severity:
blocking - Trigger: A rollup field with
functionofsum,min,max, oravgdoes not specifysourceField. - Compiler action: Compilation halts.
- Message:
"{source}: rollup function '{function}' requires sourceField"
How Diagnostics Surface
Section titled “How Diagnostics Surface”Diagnostics are included in the response body of /plan and /apply:
{ "status": "ok", "changes": [...], "summary": {...}, "diagnostics": [ { "severity": "warning", "message": "required: true with onParentDelete: setNull is invalid — auto-corrected to restrict", "source": "core_Contact.account" }, { "severity": "info", "message": "New type 'core.Individual' extends 'core.Party' — will be accepted by lookup 'core_Case.party'", "source": "core_Case.party" } ]}Even when there are no changes to apply (status: "unchanged"), diagnostics are still returned if the compiler detected any issues.
See Also
Section titled “See Also”- Concept: From Metadata to Database – where diagnostics surface in the authoring loop
- FieldDefinition – field properties that can trigger diagnostics
- ObjectDefinition – inheritance properties that can trigger diagnostics
- Metadata and Apply Pipeline – how the compiler produces diagnostics