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>
§22.4a SLICE-1 of docs/design/2026-06-06-configurable-collection-metadata.md
(§7.2). Entry metadata can live in a per-entry `<slug>.meta.yaml` sidecar with
the `.md` kept as pure prose (INV-2). Additive and non-breaking — with no
sidecars present every corpus stays on the legacy frontmatter path,
byte-identical (N=1 unchanged).
- Dual-read (app/metadata.py `read_entry`) — sidecar-else-legacy-frontmatter,
identical records (INV-6); unknown/forward-compat keys ride along through
parse→serialize and migration (INV-7, `Entry.extra`). A degenerate sidecar
(malformed/empty/slug-less) never drops the entry — slug backstopped from the
filename stem, flagged not lost (INV-3).
- Migration tool (`metadata.migrate_collection`) — idempotent, one ChangeFiles
commit per collection (new `gitea.change_files`). Tested as a function; its
Owner-gated operator trigger is DEFERRED to SLICE-4 (write paths must become
sidecar-aware first — see the design's SLICE-4 note + INV-8). No production
trigger ships here, so no corpus is rewritten.
- Malformed flag — migration 033 adds `cached_rfcs.metadata_malformed`
(additive); the corpus mirror derives it; catalog list + entry-detail APIs
surface `metadata_malformed`.
- INV-7 at graduation — graduation now carries `Entry.extra` through the rebuild
instead of dropping forward-compat keys.
Gate: backend 575 passed (28 new: test_metadata / _migration / _cache +
graduation extra-preservation). Frontend untouched. CHANGELOG 0.47.0 +
upgrade-steps; VERSION + frontend/package.json -> 0.47.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan for slice S2 of the three-tier (deployment→project→collection) refactor.
Completes acceptance @S2 (C3.6). See
docs/design/2026-06-05-three-tier-projects-collections.md Part E.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>