"""ORCH-079 (ORCH-52f): structural anti-drift for README "Известные ограничения". Layer 5 (final) of epic ORCH-52: the root README overview showcase must not lie about the project state. These are pure-text structural checks (NO `src/` import, NO agent runs) guarding two invariants (TRZ §FR-1/FR-2, AC-1/AC-2): * the OPEN limitations list is numbered strictly 1, 2, 3, … without repeats (the historical bug was `1,2,3,4,3,4`); * resolved/obsolete items (single-task worktree, in-process daemon, "Gitea CI not configured", "no retry") are NOT listed as OPEN limitations — if mentioned at all, only under the "Закрыто (история)" trail. Covers test-plan TC-05. """ import os import re _REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _README = os.path.join(_REPO_ROOT, "README.md") # Heading of the limitations section and the closed-history subsection that ends # the OPEN portion. _SECTION_HEAD = "## Известные ограничения" _CLOSED_HEAD = "Закрыто (история)" # Phrases that mark a RESOLVED/obsolete item. They must not appear in the OPEN # portion of the section (only allowed under "Закрыто (история)"). _RESOLVED_MARKERS = ( "Single-task", "shared `/repos`", "daemon-потоки", "не настроен", # "Gitea CI не настроен" "No retry", ) def _read_readme() -> str: with open(_README, encoding="utf-8") as f: return f.read() def _limitations_section() -> str: """Text of the '## Известные ограничения' section up to the next '## ' heading.""" text = _read_readme() idx = text.find(_SECTION_HEAD) assert idx != -1, "README.md has no '## Известные ограничения' section" rest = text[idx + len(_SECTION_HEAD):] # Stop at the next top-level (##) heading, if any. nxt = re.search(r"\n## ", rest) return rest[: nxt.start()] if nxt else rest def _open_portion(section: str) -> str: """The OPEN-limitations portion: everything before the 'Закрыто (история)' trail.""" cut = section.find(_CLOSED_HEAD) return section[:cut] if cut != -1 else section def test_open_limitations_numbered_sequentially(): """AC-1: the OPEN limitations list is numbered 1, 2, 3, … with no repeats/gaps.""" open_part = _open_portion(_limitations_section()) # Leading numbered list items: lines like "1. **...". numbers = [int(m) for m in re.findall(r"^(\d+)\.\s", open_part, flags=re.MULTILINE)] assert numbers, "no numbered OPEN limitations found in README section" assert numbers == list(range(1, len(numbers) + 1)), ( f"OPEN limitations numbering is not strictly sequential: {numbers}" ) def test_resolved_items_not_listed_as_open(): """AC-2: resolved/obsolete items are not present as OPEN limitations.""" open_part = _open_portion(_limitations_section()) leaked = [m for m in _RESOLVED_MARKERS if m in open_part] assert not leaked, ( f"resolved items leaked into OPEN limitations (must be under 'Закрыто'): {leaked}" ) def test_closed_history_trail_present_with_orch_refs(): """AC-2: the 'Закрыто (история)' trail exists and carries ORCH references.""" section = _limitations_section() assert _CLOSED_HEAD in section, ( "README limitations section lacks the 'Закрыто (история)' trail" ) closed = section[section.find(_CLOSED_HEAD):] assert re.search(r"ORCH-\d+", closed), ( "the 'Закрыто (история)' trail has no ORCH-NNN references" )