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:
parent
3a85a4785e
commit
5aa020f4b2
3 changed files with 21 additions and 1 deletions
|
|
@ -185,6 +185,11 @@ def _validate_extraction(data: object, index: int) -> Extraction:
|
||||||
title = _require_str(obj["title"], f"{label}.title")
|
title = _require_str(obj["title"], f"{label}.title")
|
||||||
if "\n" in title or "\r" in title:
|
if "\n" in title or "\r" in title:
|
||||||
raise ManifestError(f"{label}.title must be single-line", code="manifest_schema")
|
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")
|
okf_type = _require_str(obj["okf_type"], f"{label}.okf_type")
|
||||||
# The verdict layer is RESERVED (spec §3): the promotion gate is the only
|
# The verdict layer is RESERVED (spec §3): the promotion gate is the only
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ def test_okf_type_reserved() -> None:
|
||||||
manifest_data(extractions=[]),
|
manifest_data(extractions=[]),
|
||||||
manifest_data(source={"type": "file", "id": "UPPER", "root": "data"}),
|
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="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], max_rows=0)]),
|
||||||
manifest_data(extractions=[dict(manifest_data()["extractions"][0], query="")]),
|
manifest_data(extractions=[dict(manifest_data()["extractions"][0], query="")]),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,21 @@ def test_duplicate_extraction_ids_rejected(tmp_path: Path) -> None:
|
||||||
load(tmp_path, data)
|
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:
|
def test_bad_title_rejected(tmp_path: Path, bad_title: Any) -> None:
|
||||||
data = valid_file_manifest()
|
data = valid_file_manifest()
|
||||||
data["extractions"][0]["title"] = bad_title
|
data["extractions"][0]["title"] = bad_title
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue