v0.48.0 — SLICE-2: collection field schema + central validation (§22.4a)

Collections declare a `fields:` schema in `.collection.yaml`; entries carry
typed metadata (enum/tags/text). New central `metadata_schema` module parses
the schema leniently (INV-3) and validates entry values — advisory at read
(corpus mirror flags violations as `metadata_malformed` without hard-failing),
the enforcement point for the write boundary (edit endpoints land SLICE-4/5).

- app/metadata_schema.py: parse_fields (lenient/normalizing) + validate
- registry.parse_collection_manifest reads `fields:` into collection config
- collections.get_collection unpacks `fields`; served by the collection API
- cache._refresh_collection_corpus validates each entry advisory-only

Non-breaking, opt-in: no `fields:` → unchanged (INV-5); default `document`
collection declares none (N=1 unchanged). No DB migration — schema rides in
collections.config_json. SLICE-2 of
docs/design/2026-06-06-configurable-collection-metadata.md §7.2.

Backend suite green (601 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-07 17:06:04 -07:00
parent 98c276a662
commit e336e31812
12 changed files with 616 additions and 3 deletions
+30
View File
@@ -61,6 +61,36 @@ def test_parse_collection_manifest_rejects_bad_visibility():
registry.parse_collection_manifest("type: bdd\nvisibility: nope\n")
# ---- §22.4a SLICE-2: a `fields:` block flows into the collection config ----
def test_parse_collection_manifest_reads_field_schema():
doc = registry.parse_collection_manifest(
"type: bdd\n"
"fields:\n"
" priority:\n"
" type: enum\n"
" values: [P0, P1, P2]\n"
" tags:\n"
" type: tags\n"
)
assert doc.config["fields"] == {
"priority": {"type": "enum", "values": ["P0", "P1", "P2"]},
"tags": {"type": "tags"},
}
def test_parse_collection_manifest_lenient_on_bad_field():
# A bad field def is skipped (INV-3), the manifest still parses, and a
# manifest with no surviving fields carries no `fields` config key at all.
doc = registry.parse_collection_manifest(
"type: bdd\n"
"fields:\n"
" broken:\n"
" type: ref\n"
)
assert "fields" not in doc.config
# --- mirror discovery ---------------------------------------------------------