Add type-blind note I/O and declared per-type record modules
Problem
A single assertion-biased Frontmatter type forces both record shapes through assertion-shaped slots. The shared write path emits title/created/updated unconditionally; the shape of each record type is re-derived across the codebase.
Proposed solution
Introduce two layers, alongside the existing generic layer (nothing migrated yet):
- Type-blind note I/O:
read(path) → { fields, body } and atomic write(path, fields, body) with correct YAML quoting/escaping, plus leaf validators isValidDate and asStringList.
- Declared per-type record modules
KbAssertion and KbEvent, each owning parse/render/validate over the I/O layer. No shared base type; related only by the recordType discriminant value.
Part of the per-type record architecture epic (#848).
Acceptance criteria
Must have
Add type-blind note I/O and declared per-type record modules
Problem
A single assertion-biased
Frontmattertype forces both record shapes through assertion-shaped slots. The shared write path emitstitle/created/updatedunconditionally; the shape of each record type is re-derived across the codebase.Proposed solution
Introduce two layers, alongside the existing generic layer (nothing migrated yet):
read(path) → { fields, body }and atomicwrite(path, fields, body)with correct YAML quoting/escaping, plus leaf validatorsisValidDateandasStringList.KbAssertionandKbEvent, each owningparse/render/validate over the I/O layer. No shared base type; related only by therecordTypediscriminant value.Part of the per-type record architecture epic (#848).
Acceptance criteria
Must have
KbAssertionandKbEventeach parse, render, and validate their own field set.KbEventround-trips (parse → render → parse) carrying only its own fields — notitle/created/updatedinjected.KbAssertionround-trips unchanged.