@@ -56,6 +56,7 @@ def validate_note(
5656 schema : SchemaDefinition ,
5757 observations : list [ObservationData ],
5858 relations : list [RelationData ],
59+ frontmatter : dict | None = None ,
5960) -> ValidationResult :
6061 """Validate a note against a schema definition.
6162
@@ -64,6 +65,7 @@ def validate_note(
6465 schema: The resolved SchemaDefinition to validate against.
6566 observations: List of ObservationData from the note's observations.
6667 relations: List of RelationData from the note's relations.
68+ frontmatter: The note's frontmatter dict for settings.frontmatter validation.
6769
6870 Returns:
6971 A ValidationResult with per-field results, unmatched items, and warnings/errors.
@@ -113,6 +115,33 @@ def validate_note(
113115 else :
114116 result .warnings .append (msg )
115117
118+ # --- Validate frontmatter fields ---
119+ # Trigger: schema has frontmatter_fields and caller provided frontmatter dict
120+ # Why: settings.frontmatter rules validate metadata keys like tags, status
121+ # Outcome: frontmatter fields produce the same FieldResult/warning/error as content fields
122+ if frontmatter is not None and schema .frontmatter_fields :
123+ for fm_field in schema .frontmatter_fields :
124+ field_result = _validate_frontmatter_field (fm_field , frontmatter )
125+ result .field_results .append (field_result )
126+
127+ if field_result .status == "missing" and fm_field .required :
128+ msg = f"Missing required frontmatter key: { fm_field .name } "
129+ if schema .validation_mode == "strict" :
130+ result .errors .append (msg )
131+ result .passed = False
132+ else :
133+ result .warnings .append (msg )
134+
135+ elif field_result .status == "enum_mismatch" :
136+ msg = field_result .message or (
137+ f"Frontmatter key '{ fm_field .name } ' has invalid enum value"
138+ )
139+ if schema .validation_mode == "strict" :
140+ result .errors .append (msg )
141+ result .passed = False
142+ else :
143+ result .warnings .append (msg )
144+
116145 # --- Collect unmatched observations ---
117146 for category , values in obs_by_category .items ():
118147 if category not in matched_categories :
@@ -227,6 +256,61 @@ def _validate_enum_field(
227256 )
228257
229258
259+ # --- Frontmatter Field Validation ---
260+
261+
262+ def _validate_frontmatter_field (
263+ schema_field : SchemaField ,
264+ frontmatter : dict ,
265+ ) -> FieldResult :
266+ """Validate a single frontmatter key against a schema field declaration.
267+
268+ Checks presence and, for enum fields, value membership. Array fields
269+ collect all list items as string values.
270+ """
271+ value = frontmatter .get (schema_field .name )
272+
273+ if value is None :
274+ return FieldResult (
275+ field = schema_field ,
276+ status = "missing" ,
277+ message = f"Missing frontmatter key: { schema_field .name } " ,
278+ )
279+
280+ # --- Enum validation ---
281+ if schema_field .is_enum :
282+ str_value = str (value )
283+ if str_value not in schema_field .enum_values :
284+ allowed = ", " .join (schema_field .enum_values )
285+ return FieldResult (
286+ field = schema_field ,
287+ status = "enum_mismatch" ,
288+ values = [str_value ],
289+ message = f"Frontmatter key '{ schema_field .name } ' has invalid value: "
290+ f"{ str_value } (allowed: { allowed } )" ,
291+ )
292+ return FieldResult (
293+ field = schema_field ,
294+ status = "present" ,
295+ values = [str_value ],
296+ )
297+
298+ # --- Array / list values ---
299+ if isinstance (value , list ):
300+ return FieldResult (
301+ field = schema_field ,
302+ status = "present" ,
303+ values = [str (v ) for v in value ],
304+ )
305+
306+ # --- Scalar values ---
307+ return FieldResult (
308+ field = schema_field ,
309+ status = "present" ,
310+ values = [str (value )],
311+ )
312+
313+
230314# --- Helper Functions ---
231315
232316
0 commit comments