Skip to content

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.

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).
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.
  • Severity: warning
  • Trigger: A lookup field specifies required: true and onParentDelete: "setNull". This is contradictory – a NOT NULL column cannot be set to null on parent delete.
  • Compiler action: Auto-corrects onParentDelete to restrict.
  • Message: "required: true with onParentDelete: setNull is invalid — auto-corrected to restrict"
  • Source: {module}_{object}.{field} (e.g., core_Contact.account)
  • 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"].

  • 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.

  • Severity: blocking
  • Trigger: The extends graph 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} -> ..."
  • Severity: warning
  • Trigger: A rollup field with function: "count" specifies a sourceField. The sourceField is not used for count aggregation.
  • Compiler action: The sourceField is ignored.
  • Message: "sourceField is ignored for count rollups"
  • Source: {qualifiedName}.{fieldKey}
  • Severity: blocking
  • Trigger: An object’s extends property 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"
  • Severity: blocking
  • Trigger: A lookup field’s to property references an object that does not exist in the metadata set.
  • Compiler action: Compilation halts.
  • Message: "{source}: lookup target '{target}' — object not found"
  • Severity: blocking
  • Trigger: A rollup field’s relationship does not match any child lookup field’s toInverseKey targeting this object.
  • Compiler action: Compilation halts.
  • Message: "{source}: rollup relationship '{relationship}' — no child lookup found targeting '{object}' with toInverseKey '{relationship}'"
  • Severity: blocking
  • Trigger: A rollup field’s function is 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"
  • Severity: blocking
  • Trigger: A rollup field with function of sum, min, max, or avg does not specify sourceField.
  • Compiler action: Compilation halts.
  • Message: "{source}: rollup function '{function}' requires sourceField"

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.