GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

19. Hold every frontmatter key, and let the schema name it

Context and problem

groove validate checks a document’s frontmatter against groove-schema.toml. Until this release the schema could name five keys — title, date, topic, depth, tags — and no others: the parser deserialised the YAML block into a struct with those five fields, and serde’s default dropped every other key before anything downstream could see it. A schema naming status was refused at load time, and a “strict” mode that flags keys the schema does not name could not exist, because the validator never learned which keys the block had carried.

A user running a 74-document platform-engineering corpus put it this way (#252): a corpus with no domain metadata does not need strict mode; a corpus with domain metadata cannot use it. Their frontmatter carries twenty-one distinct keys, of which five were declarable. They proposed a one-line loader change — [fields.status] known = true — so a schema could name a key without checking it.

ADR-0010 removed a --strict flag that was accepted and discarded, and priced its return: adding it back when [options].allow_unknown_fields exists is a minor release.

The question this answers: who decides which frontmatter keys exist — the parser or the schema?

Decision drivers

Options considered

  1. known = true as a rule key (the reporter’s proposal). Rejected: on its own it cannot work, since the parser still drops the key; and once the parser retains the key it may as well retain the value, at which point known is a keyword for “an empty table”.
  2. Retain unknown values as re-serialised YAML strings. Two shapes instead of three. Rejected: a pattern would then match against YAML syntax, and a mapping would pass type = "string".
  3. Fold all five named fields into one map with accessors. Uniform, and the schema dispatch becomes one lookup. Rejected: every parser, the indexer, the server and their tests read the five fields by name today, and tags would lose the Vec<String> guarantee its type carries.
  4. Keep the five fields, add extra: BTreeMap<String, FieldValue> with three shapes. Taken.
  5. Add type = "bool". Rejected: it breaks the strings-throughout rule for one shape assertion that enum = ["true", "false"] already expresses. The documentation says so rather than leaving it to be discovered.
  6. A second regex key for mixed tag lists. Rejected: one pattern per field is a limit of the rule, and the reporter’s axis/free tag agreement is a cross-field constraint that belongs to their external validator.

Decision

Consequences

References