Skip to content

Enforce the record-type field allowlist in frontmatter validation #762

Description

@williamthorsen

Problem

The record-type schema declares required and optional field sets per record type, but validation never enforces the optional set as an allowlist. The frontmatter rule (packages/kb/src/rules/frontmatter-rule.ts) checks recordType-in-vocab, required-present, date formats, and tags-shape, but never "is this field declared?" So a note may carry any undeclared frontmatter field and pass validation: the declared field lists are advisory documentation, not an enforced contract.

This surfaced while removing the unused immutable flag (#748). Allowlist enforcement is the generic mechanism that would make a write-once record type's invariant real without a special-purpose flag: an operation that stamps a field a record type doesn't declare (e.g. updated/last-verified on an event) would be rejected for every record type, driven entirely by the existing field lists.

Context

  • The check belongs in (or beside) frontmatter-rule.ts. Record types declare required + optional in RecordTypeSchema (packages/kb/src/types.ts), and recordType is the implicit discriminant that is always allowed.
  • Existing notes carry undeclared fields today (e.g. type: howto appears on assertion fixtures). Turning this on as an error is therefore a breaking change to the current corpus. The severity (error vs warning) and whether to grandfather or migrate offending fields is the core decision this ticket must settle.
  • kb-add, capture-event, and kb-edit already validate frontmatter against the schema, so they inherit the check automatically once it lands.
  • Relates to Remove the unused immutable flag from the record-type schema #748 (removed the immutable flag in favor of this generic mechanism) and Validate a store schema against the generic kb-skill contract #746.

Proposed solution

Add an undeclared-field check that flags any frontmatter key absent from the record type's required ∪ optional ∪ {recordType}. Settle severity and rollout as part of the work: a warning first (surfaces drift without breaking existing writes), tightening to error once the corpus is clean, is the safer sequence; an error with a one-time migration that either declares or strips offending fields is the alternative. The finding names both the offending field and the record type.

Acceptance criteria

Must have

  • Validation produces a finding for a frontmatter field not declared in its record type's required/optional sets, with recordType always permitted.
  • The finding names both the offending field and the record type.
  • The severity-and-rollout decision (error vs warning; grandfathering of existing undeclared fields) is settled and reflected in the implementation.
  • New behavior is covered by tests: a record carrying an undeclared field, and a clean record that stays finding-free.

Should have

  • packages/kb/README.md documents that a record type's optional set is an enforced allowlist, not merely advisory.

Dependencies

Metadata

Metadata

Labels

featureAdded or improved external functionalityscope:kb

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions