mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
398 lines
19 KiB
Python
398 lines
19 KiB
Python
"""The governance-tier SSOT: which documents a reviewer receives in full.
|
|
|
|
Every case runs against a small fake repository that mirrors the real chapter
|
|
layout (a book entrypoint with a `## Chapters` membership list plus
|
|
`docs/<book>/NN-*.md` members), so the tiers are pinned by behaviour rather than
|
|
by the current size of the production books.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
from ouroboros.runtime_limits import REVIEW_GOVERNANCE_INLINE_SHARE
|
|
from ouroboros.tools.governance_context import (
|
|
REVIEW_PROTOCOL_CHAPTER,
|
|
GovernanceContext,
|
|
governance_context,
|
|
)
|
|
from ouroboros.utils import estimate_tokens
|
|
|
|
CHECKLIST_SECTION = "## Repo Commit Checklist\n\n1. item\n"
|
|
|
|
# Chapter bodies are addressed by what they MENTION: the selector looks for an
|
|
# exact touched file name, so `review_project_dialogue.py` must not satisfy a
|
|
# change to `project_dialogue.py`.
|
|
DEV_CHAPTERS = {
|
|
"01-naming.md": ("Naming", "Names live here. See ouroboros/project_dialogue.py twice: "
|
|
"ouroboros/project_dialogue.py."),
|
|
"02-substrings.md": ("Substrings", "This chapter only names review_project_dialogue.py "
|
|
"and chat.jsx, neither of which is touched."),
|
|
"05-review-and-commit-protocol.md": ("Protocol", "How a commit is reviewed."),
|
|
"06-widgets.md": ("Widgets", "Widget rules mention chat.js once."),
|
|
}
|
|
ARCH_CHAPTERS = {
|
|
"01-core.md": [("Core loop", "The loop mentions ouroboros/project_dialogue.py."),
|
|
("Unrelated", "Nothing touched here.")],
|
|
"02-web.md": [("Web surfaces", "The chat page is web/modules/chat.js and chat.js again.")],
|
|
}
|
|
|
|
|
|
@pytest.fixture()
|
|
def repo(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
(tmp_path / "docs" / "development").mkdir(parents=True)
|
|
(tmp_path / "docs" / "architecture").mkdir(parents=True)
|
|
(tmp_path / "BIBLE.md").write_text("# Constitution\n\nP1 Continuity.\n", encoding="utf-8", newline="\n")
|
|
(tmp_path / "docs" / "CHECKLISTS_ARCHIVE.md").write_text(
|
|
"# Archive\n\nA standing disclosure.\n", encoding="utf-8", newline="\n")
|
|
(tmp_path / "docs" / "DESIGN.md").write_text(
|
|
"# Design\n\nThe design system for web/ work.\n", encoding="utf-8", newline="\n")
|
|
|
|
for name, (title, body) in DEV_CHAPTERS.items():
|
|
(tmp_path / "docs" / "development" / name).write_text(
|
|
f"# {title}\n\nAn authored introduction.\n\n## {title} rules\n\n{body}\n",
|
|
encoding="utf-8", newline="\n")
|
|
(tmp_path / "docs" / "DEVELOPMENT.md").write_text(
|
|
"# Development\n\nThe handbook entrypoint.\n\n## Chapters\n\n"
|
|
+ "\n".join(f"- [{name}](development/{name})" for name in DEV_CHAPTERS)
|
|
+ "\n", encoding="utf-8", newline="\n")
|
|
|
|
for name, sections in ARCH_CHAPTERS.items():
|
|
body = "\n\n".join(f"## {heading}\n\n{text}" for heading, text in sections)
|
|
(tmp_path / "docs" / "architecture" / name).write_text(
|
|
f"# {name}\n\nAn authored introduction.\n\n{body}\n", encoding="utf-8", newline="\n")
|
|
(tmp_path / "docs" / "ARCHITECTURE.md").write_text(
|
|
"# Architecture\n\nThe map entrypoint.\n\n## Chapters\n\n"
|
|
+ "\n".join(f"- [{name}](architecture/{name})" for name in ARCH_CHAPTERS)
|
|
+ "\n", encoding="utf-8", newline="\n")
|
|
return tmp_path
|
|
|
|
|
|
def _context(repo: pathlib.Path, **kwargs) -> GovernanceContext:
|
|
return governance_context(
|
|
repo,
|
|
surface=kwargs.pop("surface", "triad"),
|
|
touched_paths=kwargs.pop("touched_paths", ["ouroboros/project_dialogue.py"]),
|
|
usable_window_tokens=kwargs.pop("usable_window_tokens", 1_000_000),
|
|
delivery=kwargs.pop("delivery", "packet"),
|
|
checklist_section_text=kwargs.pop("checklist_section_text", CHECKLIST_SECTION),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
def _rows(context: GovernanceContext, disposition: str = "", tier: int = 0) -> list[dict]:
|
|
return [row for row in context.manifest
|
|
if (not disposition or row["disposition"] == disposition)
|
|
and (not tier or row["tier"] == tier)]
|
|
|
|
|
|
def _paths(context: GovernanceContext, disposition: str = "", tier: int = 0) -> list[str]:
|
|
return [row["path"] for row in _rows(context, disposition, tier)]
|
|
|
|
|
|
# --- tier 1 -------------------------------------------------------------------
|
|
|
|
def test_tier_one_rules_are_always_inline(repo):
|
|
context = _context(repo)
|
|
|
|
assert _paths(context, "inline", tier=1) == [
|
|
"docs/CHECKLISTS.md", "BIBLE.md", "docs/CHECKLISTS_ARCHIVE.md"]
|
|
assert "P1 Continuity." in context.stable_inline
|
|
assert "A standing disclosure." in context.stable_inline
|
|
# The caller's checklist section is declared, never re-rendered: the surface
|
|
# owns where its own section sits in the prompt.
|
|
assert CHECKLIST_SECTION not in context.stable_inline
|
|
assert [row["chars"] for row in _rows(context, tier=1)][0] == len(CHECKLIST_SECTION)
|
|
|
|
|
|
def test_tier_one_is_the_same_text_whatever_the_change_touches(repo):
|
|
"""The cache-marked prefix must not move when the change class moves."""
|
|
web = _context(repo, touched_paths=["web/modules/chat.js"])
|
|
core = _context(repo, touched_paths=["ouroboros/project_dialogue.py"])
|
|
|
|
assert web.stable_inline == core.stable_inline
|
|
assert web.selected_inline != core.selected_inline
|
|
|
|
|
|
def test_a_document_the_surface_already_inlines_is_declared_not_duplicated(repo):
|
|
context = _context(repo, already_inline=("BIBLE.md", "docs/CHECKLISTS_ARCHIVE.md"))
|
|
|
|
assert "P1 Continuity." not in context.stable_inline
|
|
assert context.stable_inline == ""
|
|
carried = {row["path"]: row for row in _rows(context, "inline", tier=1)}
|
|
assert carried["BIBLE.md"]["chars"] == len("# Constitution\n\nP1 Continuity.\n")
|
|
assert "carried by this surface" in carried["BIBLE.md"]["reason"]
|
|
|
|
|
|
def test_a_carrying_surface_may_state_its_own_delivery_mechanism(repo):
|
|
"""The mapping identifies an actual inline location, never a future read."""
|
|
mechanism = "inlined whole in this row's constitutional system message"
|
|
context = _context(repo, already_inline={"BIBLE.md": mechanism})
|
|
|
|
bible = next(row for row in context.manifest if row["path"] == "BIBLE.md")
|
|
assert bible["disposition"] == "inline" and bible["reason"] == mechanism
|
|
assert bible["chars"] == len("# Constitution\n\nP1 Continuity.\n")
|
|
assert "P1 Continuity." not in context.stable_inline
|
|
# The archive was not declared, so this surface still receives it inline.
|
|
assert "A standing disclosure." in context.stable_inline
|
|
# A blank reason falls back to the default wording rather than to no reason.
|
|
blank = _context(repo, already_inline={"BIBLE.md": ""})
|
|
assert next(row for row in blank.manifest
|
|
if row["path"] == "BIBLE.md")["reason"] == "carried by this surface's own delivery"
|
|
|
|
|
|
def test_a_surface_with_no_checklist_section_says_so_instead_of_claiming_one(repo):
|
|
"""A deep self-review supplies no section. Recording `inline` with zero
|
|
characters, and telling the reviewer a section is inlined above, would both
|
|
be false (BIBLE P1)."""
|
|
context = _context(repo, checklist_section_text="")
|
|
|
|
row = next(row for row in context.manifest if row["path"] == "docs/CHECKLISTS.md")
|
|
assert row["disposition"] == "navigation" and row["chars"] == 0
|
|
assert row["reason"] == "this surface supplies no checklist section"
|
|
assert "NO section of it is inlined for this review" in context.navigation
|
|
assert "is inlined above" not in context.navigation
|
|
# A surface that does supply one keeps the inline row and the pointer.
|
|
supplied = _context(repo)
|
|
assert next(r for r in supplied.manifest
|
|
if r["path"] == "docs/CHECKLISTS.md")["disposition"] == "inline"
|
|
assert "is inlined above" in supplied.navigation
|
|
|
|
|
|
def test_an_unreadable_tier_one_document_is_named_not_silently_skipped(repo):
|
|
(repo / "BIBLE.md").unlink()
|
|
context = _context(repo)
|
|
|
|
assert "[⚠️ OMISSION:" in context.stable_inline
|
|
assert "BIBLE.md" in _paths(context, "inline", tier=1)
|
|
|
|
|
|
# --- tier 2 -------------------------------------------------------------------
|
|
|
|
def test_design_is_inline_only_when_a_touched_path_is_under_web(repo):
|
|
web = _context(repo, touched_paths=["web/modules/chat.js"])
|
|
core = _context(repo, touched_paths=["ouroboros/project_dialogue.py"])
|
|
|
|
assert "docs/DESIGN.md" in _paths(web, "inline", tier=2)
|
|
assert "The design system for web/ work." in web.selected_inline
|
|
assert "docs/DESIGN.md" in _paths(core, "navigation", tier=2)
|
|
assert "The design system for web/ work." not in core.selected_inline
|
|
|
|
|
|
def test_development_chapters_are_selected_by_exact_file_name(repo):
|
|
context = _context(repo, touched_paths=["ouroboros/project_dialogue.py"])
|
|
|
|
inline = _paths(context, "inline", tier=2)
|
|
assert "docs/development/01-naming.md" in inline
|
|
# A longer name that merely CONTAINS the touched basename is not a mention.
|
|
assert "docs/development/02-substrings.md" in _paths(context, "navigation", tier=2)
|
|
assert "docs/development/06-widgets.md" in _paths(context, "navigation", tier=2)
|
|
naming = next(row for row in context.manifest
|
|
if row["path"] == "docs/development/01-naming.md")
|
|
# One touched file, mentioned twice — a full path and its basename in the
|
|
# same sentence are one mention, not two.
|
|
assert naming["reason"] == "mentions 1 touched file(s), 2 time(s)"
|
|
|
|
|
|
def test_a_directory_prefix_selects_no_chapter(repo):
|
|
"""`web/` or `ouroboros/` would match nearly every chapter, so only exact
|
|
file names and repo-relative paths are searched. The DESIGN change class is
|
|
a separate, deliberate prefix rule and keeps working."""
|
|
context = _context(repo, touched_paths=["ouroboros/"])
|
|
|
|
assert _paths(context, "inline", tier=2) == [REVIEW_PROTOCOL_CHAPTER]
|
|
assert _rows(context, "inline", tier=3) == []
|
|
|
|
|
|
def test_the_review_protocol_chapter_is_inline_even_with_nothing_touched(repo):
|
|
context = _context(repo, touched_paths=[])
|
|
|
|
assert _paths(context, "inline", tier=2) == [REVIEW_PROTOCOL_CHAPTER]
|
|
assert "How a commit is reviewed." in context.selected_inline
|
|
|
|
|
|
def test_an_unassemblable_book_is_disclosed_instead_of_delivered(repo):
|
|
(repo / "docs" / "development" / "01-naming.md").unlink()
|
|
context = _context(repo)
|
|
|
|
book = next(row for row in context.manifest if row["path"] == "docs/DEVELOPMENT.md")
|
|
assert book["disposition"] == "navigation"
|
|
assert "01-naming.md" in book["reason"]
|
|
assert _paths(context, "inline", tier=2) == []
|
|
|
|
|
|
# --- the share budget ---------------------------------------------------------
|
|
|
|
def test_change_class_rules_are_admitted_protocol_then_design_then_relevance(repo):
|
|
context = _context(repo, touched_paths=["ouroboros/project_dialogue.py", "web/modules/chat.js"])
|
|
|
|
# 01-naming mentions its touched file twice, 06-widgets mentions one once.
|
|
assert _paths(context, "inline", tier=2) == [
|
|
REVIEW_PROTOCOL_CHAPTER, "docs/DESIGN.md",
|
|
"docs/development/01-naming.md", "docs/development/06-widgets.md"]
|
|
|
|
|
|
def test_the_share_budget_bounds_the_selection_and_names_the_rest(repo):
|
|
window = 200
|
|
budget = int(window * REVIEW_GOVERNANCE_INLINE_SHARE)
|
|
context = _context(repo, touched_paths=["ouroboros/project_dialogue.py", "web/modules/chat.js"],
|
|
usable_window_tokens=window)
|
|
|
|
# The admission cost is the rendered section, and a token estimate depends
|
|
# only on length, so the manifest's own chars reproduce it exactly.
|
|
spent = sum(estimate_tokens(f"## {row['path']}\n\n" + "x" * row["chars"])
|
|
for row in _rows(context, "inline") if row["tier"] > 1)
|
|
assert spent <= budget, f"tier 2/3 inline {spent} tokens exceeds the {budget}-token share"
|
|
assert _paths(context, "inline", tier=2) == [REVIEW_PROTOCOL_CHAPTER]
|
|
exhausted = [row for row in context.manifest if "inline share exhausted" in row["reason"]]
|
|
assert exhausted, "an over-budget candidate must be named as a pointer"
|
|
assert all(row["disposition"] == "navigation" for row in exhausted)
|
|
|
|
|
|
def test_a_zero_window_inlines_no_change_class_rules_and_names_all_of_them(repo):
|
|
context = _context(repo, usable_window_tokens=0)
|
|
|
|
assert _rows(context, "inline", tier=2) == []
|
|
assert _rows(context, "inline", tier=3) == []
|
|
assert REVIEW_PROTOCOL_CHAPTER in _paths(context, "navigation", tier=2)
|
|
assert context.stable_inline # tier 1 is never budgeted away
|
|
|
|
|
|
# --- tier 3 -------------------------------------------------------------------
|
|
|
|
def test_the_architecture_map_is_never_inlined_whole(repo):
|
|
context = _context(repo, touched_paths=["web/modules/chat.js"])
|
|
|
|
whole_arch = [path for path in _paths(context, "inline")
|
|
if path.startswith("docs/architecture/") and "#" not in path]
|
|
assert whole_arch == []
|
|
assert "docs/ARCHITECTURE.md" in _paths(context, "navigation", tier=3)
|
|
assert "docs/architecture/02-web.md" in context.navigation
|
|
|
|
|
|
def test_a_packet_row_receives_the_sections_that_name_a_touched_file(repo):
|
|
context = _context(repo, touched_paths=["web/modules/chat.js"], delivery="packet")
|
|
|
|
assert _paths(context, "inline", tier=3) == ["docs/architecture/02-web.md#Web surfaces"]
|
|
assert "The chat page is web/modules/chat.js" in context.selected_inline
|
|
assert "01-core.md#Unrelated" not in context.selected_inline
|
|
|
|
|
|
def test_a_retrieving_row_receives_the_navigation_and_no_sections(repo):
|
|
context = _context(repo, touched_paths=["web/modules/chat.js"], delivery="retrieving")
|
|
|
|
assert _rows(context, "inline", tier=3) == []
|
|
assert "docs/architecture/02-web.md" in context.navigation
|
|
arch = next(row for row in context.manifest if row["path"] == "docs/ARCHITECTURE.md")
|
|
assert "reads sections on demand" in arch["reason"]
|
|
|
|
|
|
# --- the whole result ---------------------------------------------------------
|
|
|
|
def test_the_navigation_tells_the_reviewer_how_to_read_what_it_did_not_receive(repo):
|
|
context = _context(repo, delivery="retrieving")
|
|
|
|
assert 'read_file(root="system_repo"' in context.navigation
|
|
assert "docs/CHECKLISTS.md" in context.navigation
|
|
assert "docs/DESIGN.md" in context.navigation # a pointer when not inlined
|
|
packet = _context(repo, delivery="packet")
|
|
assert "read_file" not in packet.navigation
|
|
assert "This row has no repository tools" in packet.navigation
|
|
assert "NOT delivered" in packet.navigation
|
|
|
|
|
|
def test_inline_whole_documents_carry_only_whole_documents(repo):
|
|
# Reference-book sources retain exact line endings, including on hosts
|
|
# where a plain read_text would silently translate CRLF back to LF.
|
|
chapter = repo / REVIEW_PROTOCOL_CHAPTER
|
|
chapter.write_bytes(chapter.read_bytes().replace(b"\n", b"\r\n"))
|
|
context = _context(repo, touched_paths=["web/modules/chat.js"])
|
|
|
|
assert all("#" not in path for path in context.inline_whole_documents)
|
|
assert context.inline_whole_documents[REVIEW_PROTOCOL_CHAPTER] == chapter.read_bytes().decode("utf-8")
|
|
assert context.inline_whole_documents["docs/DESIGN.md"] == (repo / "docs" / "DESIGN.md").read_bytes().decode("utf-8")
|
|
# A duplicate-suppressing caller compares bytes, so the map must be exact.
|
|
for path, text in context.inline_whole_documents.items():
|
|
assert (repo / path).read_bytes().decode("utf-8") == text
|
|
|
|
|
|
def test_the_result_is_deterministic_for_one_tree_and_one_change(repo):
|
|
first = _context(repo, touched_paths=["web/modules/chat.js", "ouroboros/project_dialogue.py"])
|
|
second = _context(repo, touched_paths=["web/modules/chat.js", "ouroboros/project_dialogue.py"])
|
|
|
|
assert first == second
|
|
assert first.tokens_estimate == estimate_tokens(
|
|
"\n\n".join(part for part in (first.stable_inline, first.selected_inline, first.navigation)
|
|
if part.strip()))
|
|
|
|
|
|
def test_every_document_is_dispositioned_exactly_once(repo):
|
|
context = _context(repo, touched_paths=["web/modules/chat.js"])
|
|
|
|
paths = _paths(context)
|
|
assert len(paths) == len(set(paths))
|
|
for expected in ("BIBLE.md", "docs/CHECKLISTS.md", "docs/CHECKLISTS_ARCHIVE.md",
|
|
"docs/DESIGN.md", "docs/DEVELOPMENT.md", "docs/ARCHITECTURE.md",
|
|
*(f"docs/development/{name}" for name in DEV_CHAPTERS),
|
|
*(f"docs/architecture/{name}" for name in ARCH_CHAPTERS)):
|
|
assert expected in paths
|
|
assert all(set(row) == {"path", "tier", "disposition", "chars", "reason"}
|
|
for row in context.manifest)
|
|
|
|
|
|
# --- the triad wiring ---------------------------------------------------------
|
|
|
|
def test_the_triad_packet_declares_bible_and_the_archive_as_already_delivered(repo, monkeypatch):
|
|
"""`review_multi_model.triad_api_messages` prepends BIBLE.md to every api row
|
|
and `_load_checklist_section` appends the standing disclosures, so the triad
|
|
must not send either a second time."""
|
|
from ouroboros.review_records import ReviewSlot
|
|
from ouroboros.tools import review
|
|
|
|
monkeypatch.setattr(review, "reviewer_context_window", lambda *_a, **_k: 200_000)
|
|
ctx = type("_Ctx", (), {"repo_dir": str(repo)})()
|
|
context = review._triad_governance_context(
|
|
ctx, ["web/modules/chat.js"], CHECKLIST_SECTION,
|
|
["openai/packet"], [ReviewSlot(slot_id="triad_slot_1", model="openai/packet")])
|
|
|
|
assert context.stable_inline == ""
|
|
assert all("carried by this surface" in row["reason"]
|
|
for row in context.manifest if row["path"] in ("BIBLE.md", "docs/CHECKLISTS_ARCHIVE.md"))
|
|
assert "docs/DESIGN.md" in [row["path"] for row in context.manifest
|
|
if row["disposition"] == "inline"]
|
|
|
|
|
|
def test_a_panel_with_no_api_row_asks_for_no_packet_governance(repo):
|
|
from ouroboros.tools import review
|
|
|
|
ctx = type("_Ctx", (), {"repo_dir": str(repo)})()
|
|
context = review._triad_governance_context(ctx, ["a.py"], CHECKLIST_SECTION, [], [])
|
|
|
|
assert context == GovernanceContext()
|
|
assert context.manifest == [] and context.navigation == ""
|
|
|
|
|
|
def test_retrieving_triad_receives_shared_tiers_in_its_actual_task(repo, monkeypatch):
|
|
from ouroboros.review_records import ReviewSlot
|
|
from ouroboros.tools import review
|
|
|
|
monkeypatch.setattr(review, "reviewer_context_window", lambda *_a, **_k: 200_000)
|
|
ctx = type("_Ctx", (), {"repo_dir": str(repo)})()
|
|
archive = (repo / "docs/CHECKLISTS_ARCHIVE.md").read_text(encoding="utf-8")
|
|
checklist = CHECKLIST_SECTION + archive
|
|
context = review._triad_governance_context(
|
|
ctx, ["web/modules/chat.js"], checklist, ["openai/native"],
|
|
[ReviewSlot(slot_id="triad_1", model="openai/native")], delivery="retrieving")
|
|
task = review._triad_session_task(
|
|
ctx, goal_section="goal", scope_section="scope", checklist_section=checklist,
|
|
rebuttal_section="", review_history_section="", governance=context)
|
|
|
|
assert task.count("P1 Continuity.") == 1
|
|
assert task.count("A standing disclosure.") == 1
|
|
assert "The design system for web/ work." in task
|
|
assert "How a commit is reviewed." in task
|
|
assert "docs/architecture/02-web.md" in task
|
|
assert "The chat page is web/modules/chat.js" not in task
|
|
assert 'read_file(root="system_repo"' in task
|