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

@ -138,6 +138,7 @@ def test_okf_type_reserved() -> None:
manifest_data(extractions=[]),
manifest_data(source={"type": "file", "id": "UPPER", "root": "data"}),
manifest_data(extractions=[dict(manifest_data()["extractions"][0], title="two\nlines")]),
manifest_data(extractions=[dict(manifest_data()["extractions"][0], title="link [x]")]),
manifest_data(extractions=[dict(manifest_data()["extractions"][0], max_rows=0)]),
manifest_data(extractions=[dict(manifest_data()["extractions"][0], query="")]),
],

View file

@ -255,7 +255,21 @@ def test_duplicate_extraction_ids_rejected(tmp_path: Path) -> None:
load(tmp_path, data)
@pytest.mark.parametrize("bad_title", ["two\nlines", "carriage\rreturn", "", 7, None])
@pytest.mark.parametrize(
"bad_title",
[
"two\nlines",
"carriage\rreturn",
"",
7,
None,
# `[`/`]` break index-link and navigation parsing (ingest spec §4/§6):
# rejected fail-fast at manifest load so downstream verbatim rendering is safe.
"opening [bracket",
"closing ]bracket",
"[wrapped]",
],
)
def test_bad_title_rejected(tmp_path: Path, bad_title: Any) -> None:
data = valid_file_manifest()
data["extractions"][0]["title"] = bad_title