feat(manifest): reject [/] in extraction title (ingest-spec §4)

Ingest-spec §4 (ratified D1, commons bfa5a9b) requires a title to reject
`[` and `]` fail-fast at manifest load. The title is rendered verbatim into
the index link label and frontmatter (§5/§6), where a bracket would break
index-link and navigation parsing (method-spec §3 Step 1). The rule was
specced but unenforced: validation only checked single-line.

Satisfies the commons "Title link-safety" load-bearing §11 seam. Same
`manifest_schema` error code as the sibling single-line/max_rows rules;
parametrized cases added to test_manifest and test_error_codes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016oMpAhQcJZVGBW182stSPn
This commit is contained in:
Kjell Tore Guttormsen 2026-07-23 21:33:47 +02:00
commit 5aa020f4b2
3 changed files with 21 additions and 1 deletions

View file

@ -185,6 +185,11 @@ def _validate_extraction(data: object, index: int) -> Extraction:
title = _require_str(obj["title"], f"{label}.title")
if "\n" in title or "\r" in title:
raise ManifestError(f"{label}.title must be single-line", code="manifest_schema")
# `[`/`]` would break index-link and navigation parsing (ingest spec §4); the
# title is rendered verbatim into the index link and frontmatter (§5/§6), so the
# invariant is met by fail-fast validation here, never by downstream repair.
if "[" in title or "]" in title:
raise ManifestError(f"{label}.title must not contain '[' or ']'", code="manifest_schema")
okf_type = _require_str(obj["okf_type"], f"{label}.okf_type")
# The verdict layer is RESERVED (spec §3): the promotion gate is the only