Files
undercooked/tests/unit/test_docs_index.py
T
ducoterra 6fd83446ec phase: 01_runnable_service_scaffold
**Phase 01 final verification pass — all criteria green**

- Verified (nothing left to implement; all 4 task files already in `complete/`): toolchain lock, typed `Settings`+`.env.example`, debugpy bootstrap, app factory, CLI (`--host/--port/--reload/--levels-dir`, `python -m undercooked`), static shell + `/static`, security headers, no-CDN/doc guards, `validate.sh`.
- `uv sync --extra dev` → exit 0; `uv.lock` present (untracked → harness commits it with the phase).
- `uv run undercooked --host 127.0.0.1 --port 8000` → `curl /healthz` = 200 `{"status":"ok","version":"0.1.0","rooms":0}` (superset of the criterion's body — now documented in README); `curl /` = 200 shell HTML; static css/js 200; CSP/nosniff/no-referrer/DENY on 200, 404 and 405 too.
- `DEBUGPY=1`: port 5678 is held on this machine by the user's own podman dev-container debugpy adapter — **no live change made/killed**; verified via `DEBUGPY=1 DEBUGPY_PORT=5679` → listener opens, `/healthz` still 200; the occupied-port case degraded gracefully as designed (warning, server serves). Bad config → one line + exit 2 ✓.
- `bash .agents/validate.sh` (root **and** `.agents/`) → exit 0: `ruff check .` 0 errors, `pyright` 0 errors, levels-CLI notice-skip (matches README prose), **284 passed** full suite incl. 10 Playwright e2e, coverage **100.00%** (floor 90), repo hygiene clean. `uv run pytest -m e2e` → 10 passed.
- Docs fixed: README Status → phase 01 complete + next pending 02 (was pointing at `todo/01…`), documented real `/healthz` body, precedence now "CLI flags > env > .env > default", added the PLAN §13-promised **How to play** section (honest about shell-only today), Layout rows marked what exists vs phases 02/07. AGENTS.md: recorded the `data:`-URL exemption in the no-CDN rule (test allows it) and matched the session-token rule to the browser's WebCrypto/base64url implementation. Also corrected a stale "session-wide chromium" docstring in `tests/e2e/conftest.py` (fixtures are function-scoped).
- Deviations: no container build run (needs an image pull; not a phase criterion — `Containerfile`/`compose.yaml` reviewed and consistent with `docs/DEPLOYMENT.md`); `HEAD /` is 405 (routes are GET-only by design; health checks use GET). No git index/history commands run.
- Next pending phase: `02_level_content_and_validation`.
2026-10-06 01:51:20 -04:00

358 lines
14 KiB
Python

"""PLAN §13 doc-index guard: the promised documentation set and the `docs/` tree cannot drift.
`.agents/PLAN.md` §13 closes with the canonical documentation index and the sentence
"`tests/unit/test_docs_index.py` (phase 01) keeps this table and the `docs/` tree identical,
so neither can drift silently." This is that file. It closes both directions of drift:
* **A promised doc is missing.** Every index row whose creating phase has already completed
(according to the `.agents/phases/complete/` tree) must exist on disk, so no phase can
reference a document that was never written. A range in the "Created by" column —
`phase 02/03` — promises the file by the *end* of that range, so its obligation starts at
the later phase; a `→` marks the stub-then-fill pattern, where the earlier phase owes the
stub.
* **A doc exists that nothing promises.** Every `docs/*.md` must be listed in the index or be
a document a phase file of the roadmap itself creates — `docs/ROOMS.md` comes from phase 05
and is not in §13, so the roadmap may extend the doc set, but an untracked file may not
appear there silently.
Only markdown is compared: the index promises *documents*, and a diagram or fixture a phase
puts under `docs/` is not a documentation promise.
"""
from __future__ import annotations
import re
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
import pytest
pytestmark = pytest.mark.unit
REPO_ROOT = Path(__file__).resolve().parents[2]
PLAN = REPO_ROOT / ".agents/PLAN.md"
DOCS_DIR = REPO_ROOT / "docs"
PHASES_DIR = REPO_ROOT / ".agents/phases"
COMPLETE_DIR = PHASES_DIR / "complete"
INDEX_HEADING = "### Documentation index (canonical)"
PHASE_DIRECTORY = re.compile(r"^(\d{2})_")
PHASE_REFERENCE = re.compile(r"phase\s+(\d{1,2})", re.IGNORECASE)
NUMBER = re.compile(r"\d{1,2}")
DOC_REFERENCE = re.compile(r"docs/[A-Za-z0-9_-]+\.md")
#: The index exactly as PLAN §13 writes it. Changing this set means editing PLAN.md first.
CANONICAL_DOCS = frozenset(
{
"README.md",
"AGENTS.md",
"BUDGETS.md",
"docs/DESIGN.md",
"docs/SIMULATION.md",
"docs/LEVELS.md",
"docs/PROTOCOL.md",
"docs/CLIENT.md",
"docs/DEPLOYMENT.md",
"docs/TESTING.md",
"docs/ACCESSIBILITY.md",
"docs/SECURITY.md",
"tests/e2e/README.md",
}
)
@dataclass(frozen=True, slots=True)
class DocRow:
"""One row of the PLAN §13 index: path, contents, and the phase(s) that create it."""
path: str
contents: str
created_by: str
@property
def promised_from(self) -> int:
"""First phase after which this file must exist.
The first comma/arrow-separated group is the creation promise. Inside it, a slash
range (`phase 02/03`) is owed by the end of the range, so the later number wins;
anything else is owed by the earliest number mentioned.
"""
first_group = re.split(r"[→,;]", self.created_by, maxsplit=1)[0]
numbers = [int(number) for number in NUMBER.findall(first_group)]
if not numbers:
numbers = [int(number) for number in PHASE_REFERENCE.findall(self.created_by)]
if not numbers:
pytest.fail(f"PLAN index row {self.path!r} names no phase: {self.created_by!r}")
return numbers[-1] if "/" in first_group and len(numbers) > 1 else numbers[0]
def parse_documentation_index(plan_text: str) -> list[DocRow]:
"""Read the canonical documentation-index table out of PLAN.md text."""
lines = plan_text.splitlines()
try:
start = lines.index(INDEX_HEADING)
except ValueError: # pragma: no cover - PLAN is locked; fail loudly if it is renamed
pytest.fail(f".agents/PLAN.md has no {INDEX_HEADING!r} heading")
rows: list[DocRow] = []
for raw in lines[start + 1 :]:
stripped = raw.strip()
if not stripped.startswith("|"):
if rows:
break
continue
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
if len(cells) < 3 or cells[0].strip("`") in {"Path", ""} or set(cells[0]) <= {"-", ":"}:
continue
rows.append(DocRow(path=cells[0].strip("`"), contents=cells[1], created_by=cells[2]))
return rows
def latest_completed_phase(complete_dir: Path = COMPLETE_DIR) -> int:
"""Highest phase number with anything in `complete/` (0 before the first phase finishes)."""
return max(
(
int(match.group(1))
for entry in complete_dir.glob("*")
if (match := PHASE_DIRECTORY.match(entry.name))
),
default=0,
)
def roadmap_promised_docs(phases_dir: Path = PHASES_DIR) -> set[str]:
"""Every `docs/*.md` a phase overview or task file itself names."""
return {
match.group(0)
for phase_file in sorted(phases_dir.rglob("*.md"))
for match in DOC_REFERENCE.finditer(phase_file.read_text(encoding="utf-8"))
}
def documentation_index(plan: Path = PLAN) -> list[DocRow]:
return parse_documentation_index(plan.read_text(encoding="utf-8"))
def written_docs(docs_dir: Path = DOCS_DIR, *, root: Path = REPO_ROOT) -> set[str]:
"""The markdown currently in `docs/`, as repo-relative paths."""
return {path.relative_to(root).as_posix() for path in sorted(docs_dir.rglob("*.md"))}
# --------------------------------------------------------------------------- the index itself
def test_the_documentation_index_is_parseable() -> None:
rows = documentation_index()
assert len(rows) >= len(CANONICAL_DOCS), rows
paths = [row.path for row in rows]
assert len(paths) == len(set(paths)), f"duplicate index rows: {paths}"
assert all(row.contents and row.created_by for row in rows), paths
def test_the_index_still_lists_the_canonical_doc_set() -> None:
"""Adding or dropping a row is a PLAN §13 change and must fail a test first."""
assert {row.path for row in documentation_index()} == CANONICAL_DOCS
@pytest.mark.parametrize(
("created_by", "expected"),
[
("phase 01", 1),
("phase 01, refined throughout", 1),
("phase 01 (stub) \u2192 phase 09", 1),
("phase 02, phase 11", 2),
("phase 02/03", 3),
("phase 14", 14),
("refined throughout, phase 05", 5),
],
)
def test_the_promised_from_rule_reads_the_phase_column(created_by: str, expected: int) -> None:
row = DocRow(path="docs/X.md", contents="whatever", created_by=created_by)
assert row.promised_from == expected
def test_an_index_row_that_names_no_phase_fails_loudly() -> None:
row = DocRow(path="docs/X.md", contents="whatever", created_by="someday, maybe")
with pytest.raises(pytest.fail.Exception, match="names no phase"):
_ = row.promised_from
# --------------------------------------------------------------------------- promised vs present
def test_every_doc_promised_by_a_completed_phase_exists() -> None:
"""No phase may reference a document whose creating phase is already in `complete/`."""
completed = latest_completed_phase()
assert completed >= 1, "no completed phase found — the guard would be vacuous"
missing = [
f"{row.path} (owed by phase {row.promised_from:02d}, {completed:02d} is complete)"
for row in documentation_index()
if row.promised_from <= completed and not (REPO_ROOT / row.path).is_file()
]
report = "\n ".join(missing)
assert missing == [], f"documentation the project owes but does not have:\n {report}"
def test_no_doc_file_exists_that_nothing_promises() -> None:
"""The `docs/` tree is exactly the index plus what the phase roadmap creates."""
promised = {row.path for row in documentation_index()} | roadmap_promised_docs()
orphans = sorted(written_docs() - promised)
report = "\n ".join(orphans)
assert orphans == [], (
f"unpromised files under docs/ — add each to PLAN §13's index or to the phase "
f"that creates it:\n {report}"
)
def test_the_docs_on_disk_are_the_three_phase_01_documents() -> None:
"""Pins today's tree so that adding a document is a deliberate, named act."""
assert written_docs() == {"docs/DESIGN.md", "docs/DEPLOYMENT.md", "docs/TESTING.md"}
def stub_without_owner(relative: str, text: str) -> str | None:
"""One sentence of complaint about a document, or `None` when it is honest.
Split out of the test below so a planted stub can prove each check fires; a guard that
only ever sees healthy input is not a guard.
"""
if not text.startswith("# "):
return f"{relative} does not open with a title"
if "## Purpose" not in text:
return f"{relative} has no Purpose section"
purpose = text.split("## Purpose", 1)[1].split("\n##", 1)[0].strip()
if len(purpose.split()) < 25:
return f"{relative}'s Purpose paragraph is too thin to guide"
names_stub = re.search(r"\bstub\b", text, re.IGNORECASE) is not None
has_owner = re.search(r"[Ff]illed in by phase \d\d", text) is not None
if names_stub and not has_owner:
return f"{relative} is a stub with no 'Filled in by phase NN' marker"
return None
def dishonest_references(
references: dict[str, set[str]], *, exists: Callable[[str], bool], owed_later: set[str]
) -> list[str]:
"""`document: path` for each cited doc that neither exists nor is owed by a later phase."""
offenders: list[str] = []
for name, cited in sorted(references.items()):
offenders.extend(
f"{name}: {path}"
for path in sorted(cited)
if not exists(path) and path not in owed_later
)
return offenders
def test_the_stub_docs_say_who_fills_them_in() -> None:
"""A stub with no owner stays a stub forever, so each one names its phase."""
complaints = [
complaint
for relative in sorted(written_docs())
if (complaint := stub_without_owner(relative, (REPO_ROOT / relative).read_text("utf-8")))
]
report = "\n ".join(complaints)
assert complaints == [], f"documents that are not self-describing:\n {report}"
def test_readme_and_agents_reference_only_honest_doc_paths() -> None:
"""A doc path in README/AGENTS either exists or is owed by a phase that has not run."""
completed = latest_completed_phase()
rows = documentation_index()
owed_later = {row.path for row in rows if row.promised_from > completed}
owed_later |= roadmap_promised_docs()
root_docs = {row.path for row in rows if not row.path.startswith("docs/")}
references: dict[str, set[str]] = {}
for name in ("README.md", "AGENTS.md"):
text = (REPO_ROOT / name).read_text(encoding="utf-8")
references[name] = set(DOC_REFERENCE.findall(text)) | {
path for path in root_docs if path in text
}
offenders = dishonest_references(
references, exists=lambda path: (REPO_ROOT / path).is_file(), owed_later=owed_later
)
report = "\n ".join(offenders)
assert offenders == [], f"references to documentation nothing promises to create:\n {report}"
# --------------------------------------------------------------------------- the guard itself
def test_a_missing_promised_doc_is_named(tmp_path: Path) -> None:
"""Plant a row whose phase already shipped: the parse must expose it as owed."""
plan = tmp_path / "PLAN.md"
plan.write_text(
"\n".join(
[
"## Risks",
"",
INDEX_HEADING,
"",
"| Path | Contents | Created by |",
"|---|---|---|",
"| `docs/GONE.md` | whatever it says | phase 01 |",
"| `docs/LATER.md` | not owed yet | phase 09 |",
"",
"prose after the table",
]
),
encoding="utf-8",
)
rows = parse_documentation_index(plan.read_text(encoding="utf-8"))
assert [row.path for row in rows] == ["docs/GONE.md", "docs/LATER.md"]
owed = [row.path for row in rows if row.promised_from <= 1]
assert owed == ["docs/GONE.md"]
assert not (tmp_path / owed[0]).is_file()
def test_an_unpromised_doc_file_is_reported(tmp_path: Path) -> None:
"""The orphan rule works on a temp tree too — it is not README-specific magic."""
(tmp_path / "docs").mkdir()
(tmp_path / "docs" / "WANTED.md").write_text("# WANTED\n", encoding="utf-8")
(tmp_path / "docs" / "MYSTERY.md").write_text("# MYSTERY\n", encoding="utf-8")
(tmp_path / "docs" / "tile.svg").write_text("<svg/>", encoding="utf-8")
assert written_docs(tmp_path, root=tmp_path) == {
"docs/MYSTERY.md",
"docs/WANTED.md",
}
assert written_docs(tmp_path, root=tmp_path) - {"docs/WANTED.md"} == {"docs/MYSTERY.md"}
def test_an_unhonest_document_is_described_precisely() -> None:
"""Each complaint the stub check can make is exercised on planted text."""
body = "word " * 30
good = f"# Title\n\n## Purpose\n{body}\nA stub here. Filled in by phase 09.\n"
assert stub_without_owner("docs/GOOD.md", good) is None
assert stub_without_owner("docs/ORPHAN.md", good.replace(" Filled in by phase 09.", "")) == (
"docs/ORPHAN.md is a stub with no 'Filled in by phase NN' marker"
)
assert stub_without_owner("docs/UNTITLED.md", f"## Purpose\n{body}") == (
"docs/UNTITLED.md does not open with a title"
)
assert stub_without_owner("docs/NOPURPOSE.md", "# Title\nProse only.\n") == (
"docs/NOPURPOSE.md has no Purpose section"
)
assert stub_without_owner("docs/THIN.md", "# Title\n\n## Purpose\ntoo short\n") == (
"docs/THIN.md's Purpose paragraph is too thin to guide"
)
def test_a_reference_to_a_doc_nobody_promises_is_named() -> None:
references = {
"AGENTS.md": {"docs/HERE.md"},
"README.md": {"docs/HERE.md", "docs/GHOST.md", "docs/OWED.md"},
}
offenders = dishonest_references(
references,
exists=lambda path: path in {"docs/HERE.md", "docs/OWED.md"},
owed_later={"docs/OWED.md"},
)
assert offenders == ["README.md: docs/GHOST.md"]
def test_the_roadmap_extension_of_the_doc_set_is_tracked() -> None:
"""`docs/ROOMS.md` is phase 05's, not PLAN §13's — and must not read as an orphan."""
roadmap = roadmap_promised_docs()
assert "docs/ROOMS.md" in roadmap
assert "docs/ROOMS.md" not in CANONICAL_DOCS
assert latest_completed_phase() >= 1