ouroboros/ouroboros/review.py
Ouroboros d1c8fca451 v7next F1: domain D11 quiet part - server.py six-leaf split from tip bytes; restart transaction hot-deferred; same-qualname ratchet delta landed
Module side. 34 D11 owners classified: 13 byte-identical across
tip/merge-base/reference (client_surface, gateway __init__/files/logs/mcp/
onboarding_host/schedules/task_events/task_hurry/ui_preferences, server_auth,
server_entrypoint, server_web); 17 pure upstream drift - tip bytes stand
(gateway _helpers/claudexor_accounts/contracts/control/extensions/history/
host_service/marketplace/models/presence_settings/projects/router/
skill_publish/state/tasks/ws, server_runtime); 3 carry only the D03
settings-seam / D04 retired-knob reference deltas - HOT-DEFERRED with that
seam (gateway owner_settings/onboarding/settings, D12/D17 precedent);
server.py split.

The split: 6 leaves, 43 moved spans (rows 1034-1078 + 3948-3949), every span
transplant-tool proof-green against git show HEAD:server.py (ast=tokens=
byte-roundtrip on every symbol, leaf_invariants=[], zero declared names - the
reference design homes shared rebindable state in server_process, so all six
are projection-only leaves, no handles). server.py 3191->1640: server_process
(drive root, logger, restart signals), server_routing_context (13 owner-turn
projections), server_owner_routing (attachment staging, mailbox delivery,
routing receipt, owner-message dispatch, /evolve off), server_liveness (WS3
wedge watchdog), server_maintenance (startup sweeps + periodic cadences),
server_restart (live census, teardown args, update guards, bus shutdown).
Facade = tip parent - moved spans + reference-style top import block (module-
level PORT_FILE/logging reads force top imports), facade audit green: every
kept span byte-identical to tip, every moved name re-exported by identity.
Drift-probe first per leaf: reference leaves byte-true except 10 spans
falsified by upstream drift (attachment-report train, OB-03 monotonic clock,
child-ref promotion, planned-handoff train) - re-emitted from tip bytes, no
oracle semantics replayed over drift.

HOT-DEFERRED with evidence: rows 1070/1072/1073/1074 (_pending_restart,
_handle_restart_in_supervisor, _check_pending_restart_drain,
_perform_supervisor_restart) - the upstream delegation train coupled the
restart performer to main() through the written module global
_planned_delegate_restart_transaction_id; byte-preserving relocation would
fork that state (D09-class second answer about ownership); inventory pinned
in test_server_extraction._SERVER_OWNED. Rows 1080-1081 (lifespan, D03
settings-seam server half) - reader halves 913-917 are hot-deferred by
D12/D17 (settings_integrity rewrite); landing the boot half alone would leave
normalization neither persisted nor re-derived. Gateway ABI/alias rows = F3;
web/ untouched.

LIVE delta landed: the same-qualname FUNCTION_DEBT relocation rule (row 1033,
delta id D11) replayed byte-identical from the reference into the tip-shaped
validate_manifest_transition (ouroboros/review.py is NOT a protected file;
the reference-only MODULE_DEBT_1500 layer NOT replayed - Q11=B). Pin renamed
per the row with reference bytes. This unblocks the D08 lane's row 2016
deferral (FUNCTION_DEBT relocation of _handle_schedule_task).

Test side: pin suite test_server_extraction (6, reference-adapted: deferred
restart rows moved to the _SERVER_OWNED work order, prune-sweep rows 3948-3949
added to _MOVED_OWNERS, facade bound 1700 while the restart organ is
deferred); rows 1192/1259 landed as the D11 slice of the reconciliation theme
split (the two server_maintenance-bound tests re-homed - the byte-debt
ratchet refused the +40-byte in-place retarget, giant 320340->318310); owner
retargets mirrored path-keyed (run_isolation DATA_DIR, phase3c maintenance
owner, project_routing_v664 routing_context, client_surface joined-text pin,
ws3 fake clock -> server_liveness, panic-sweep leaf floor 5->11); patches of
facade-resident readers deliberately NOT retargeted (they ride the deferral).
All touched test files lossless (the one rename is ledger row 1033); no new
ast-identical dup bodies.

size-ratchet manifest regenerated with the official tool (one byte-debt
shrink); ratchet lane 4 passed + 1 pre-existing base red reproduced bit-for-
bit on pristine a56bb76a (parent-pair manifest/tree mismatch at 7d2dca49,
documented in LEDGER_CORRECTIONS 9) - the (a56bb76a -> this commit) pair is
consistent. ruff check . --select F clean. CI-shape battery on this tree:
parallel 11983 passed rc=0, serial 609 passed rc=0. Import smoke
green (identity re-exports, shared Events single home).
docs/v7next/LEDGER_CORRECTIONS.md: D11 lane section (10 entries).

(cherry picked from commit 849f90be0c3b554753b2c580d14ac70450b2c58c)
2026-08-31 00:17:05 +00:00

1014 lines
43 KiB
Python

"""Repository size-ratchet inventory and complexity metrics."""
from __future__ import annotations
import ast
import contextlib
import hashlib
import os
import pathlib
import subprocess
import tempfile
from dataclasses import dataclass, field
from typing import Any, Dict, Iterable, Iterator, List, Mapping, Tuple
from ouroboros.tools.review_helpers import _VENDORED_NAMES, _VENDORED_SUFFIXES
from ouroboros.tools.review_helpers import iter_repo_pack_entries # noqa: F401 - public compatibility re-export
TARGET_MODULE_LINES = 1000
MAX_MODULE_LINES = 1600
BAND_MODULE_MAX_LINES = 1500
MAX_MODULE_BYTES = 200_000
TARGET_FUNCTION_LINES = 150
MAX_FUNCTION_LINES = 300
# Owner decision 2026-08-21: keep this only as a high-water alarm with ample
# product headroom; module and per-function ratchets remain the primary gates.
MAX_TOTAL_FUNCTIONS = 9500
SIZE_RATCHET_MANIFEST_PATH = "ouroboros/size_ratchet_manifest.py"
# Module inventory covers tests/devtools. Only generated, vendored, or environment
# directories remain outside the source ratchet.
_MODULE_SKIP_DIR_NAMES = frozenset(
{
".git",
".mypy_cache",
".pytest_cache",
".venv",
"__pycache__",
"assets",
"build",
"dist",
"node_modules",
"python-standalone",
"venv",
}
)
# Function inventory intentionally preserves the pre-v7 runtime-health scope.
_FUNCTION_SKIP_DIR_NAMES = _MODULE_SKIP_DIR_NAMES | frozenset({"devtools", "tests"})
FUNCTION_COUNT_EXCLUDED_FILES = frozenset({"app.py", "demo_app.py", "launcher.py"})
@dataclass(frozen=True, order=True)
class GatedModule:
"""One strict-UTF-8 source module in the deterministic size inventory."""
path: str
line_count: int
utf8_bytes: int
_source_text: str = field(default="", repr=False, compare=False)
@dataclass(frozen=True, order=True)
class GatedFunction:
"""One Python function keyed by exact path and lexical qualname."""
path: str
qualname: str
line_start: int
line_count: int
@dataclass(frozen=True)
class SizeRatchetManifest:
"""Parsed data-only size ratchet manifest."""
baseline_source_sha: str
giant_paths: frozenset[str]
function_debt: frozenset[tuple[str, str]]
band_baseline_paths: frozenset[str]
band_paths: Mapping[str, str | None]
byte_baseline_debt: Mapping[str, int]
byte_debt: Mapping[str, int]
@dataclass(frozen=True)
class SizeRatchetInventory:
"""Exact live debt derived from one candidate tree."""
modules: tuple[GatedModule, ...]
functions: tuple[GatedFunction, ...]
giant_paths: frozenset[str]
function_debt: frozenset[tuple[str, str]]
band_paths: frozenset[str]
byte_debt: Mapping[str, int]
def _exact_repo_relative_path(raw: str | pathlib.Path) -> str:
text = str(raw)
posix = pathlib.PurePosixPath(text)
if (
"\\" in text
or posix.is_absolute()
or not posix.parts
or any(part in {"", ".", ".."} for part in posix.parts)
or text != posix.as_posix()
):
raise ValueError(f"path is not a canonical repo-relative path: {raw!r}")
return posix.as_posix()
def _section_repo_relative_path(raw: str | pathlib.Path) -> str:
"""Normalize the legacy ``collect_sections`` display prefix only."""
text = str(raw).replace("\\", "/")
if text.startswith("repo/"):
text = text[len("repo/") :]
return _exact_repo_relative_path(text)
def is_gated_js_module(path: str) -> bool:
"""Return whether an exact repo-relative path is first-party web JavaScript."""
rel = _exact_repo_relative_path(path)
posix = pathlib.PurePosixPath(rel)
if not (rel.startswith("web/") and rel.endswith(".js")):
return False
if any(part in _MODULE_SKIP_DIR_NAMES for part in posix.parts[:-1]):
return False
name = posix.name
return name not in _VENDORED_NAMES and not any(name.endswith(suffix) for suffix in _VENDORED_SUFFIXES)
def _is_gated_module_path(path: str) -> bool:
posix = pathlib.PurePosixPath(path)
if any(part in _MODULE_SKIP_DIR_NAMES for part in posix.parts[:-1]):
return False
return path.endswith(".py") or is_gated_js_module(path)
def _filesystem_repo_paths(repo_dir: pathlib.Path) -> Iterator[str]:
for raw_root, dirs, files in os.walk(repo_dir, topdown=True, followlinks=False):
dirs[:] = sorted(name for name in dirs if name not in _MODULE_SKIP_DIR_NAMES)
root = pathlib.Path(raw_root)
for name in sorted(files):
yield (root / name).relative_to(repo_dir).as_posix()
def candidate_repo_paths(repo_dir: pathlib.Path) -> tuple[str, ...]:
"""Return cached and nonignored untracked paths for a Git candidate tree."""
root = pathlib.Path(repo_dir).resolve()
try:
top_level = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
cwd=root,
check=False,
capture_output=True,
)
except OSError:
top_level = None
if top_level is None or top_level.returncode != 0:
return tuple(_filesystem_repo_paths(root))
git_root = pathlib.Path(top_level.stdout.decode("utf-8").strip()).resolve()
if git_root != root:
return tuple(_filesystem_repo_paths(root))
result = subprocess.run(
["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z"],
cwd=root,
check=True,
capture_output=True,
)
paths = (item.decode("utf-8") for item in result.stdout.split(b"\0") if item)
return tuple(sorted(path for path in paths if root.joinpath(*pathlib.PurePosixPath(path).parts).exists()))
def iter_gated_modules(
repo_dir: pathlib.Path,
*,
repo_paths: Iterable[str] | None = None,
) -> Iterator[GatedModule]:
"""Yield the canonical source inventory.
The default Git candidate sees cached and nonignored untracked paths. A
deterministic filesystem fallback supports non-Git fixtures, while callers
may inject an exact merge-base inventory.
"""
root = pathlib.Path(repo_dir).resolve()
injected = repo_paths is not None
raw_paths = candidate_repo_paths(root) if repo_paths is None else repo_paths
normalized: set[str] = set()
for raw_path in raw_paths:
rel = _exact_repo_relative_path(raw_path)
if _is_gated_module_path(rel):
normalized.add(rel)
for rel in sorted(normalized):
path = root.joinpath(*pathlib.PurePosixPath(rel).parts)
if not path.exists():
if injected:
raise ValueError(f"injected gated source path does not exist: {rel}")
continue
if path.is_symlink() or not path.is_file():
raise ValueError(f"gated source path must be a regular file: {rel}")
try:
path.resolve().relative_to(root)
except ValueError as exc:
raise ValueError(f"gated source path escapes repository: {rel}") from exc
raw = path.read_bytes()
text = raw.decode("utf-8").replace("\r\n", "\n").replace("\r", "\n")
yield GatedModule(rel, len(text.splitlines()), len(text.encode("utf-8")), text)
def _iter_lexical_functions(tree: ast.AST, path: str) -> Iterator[GatedFunction]:
def visit(node: ast.AST, scope: tuple[str, ...]) -> Iterator[GatedFunction]:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
qualname = ".".join((*scope, node.name))
if node.end_lineno is None:
raise ValueError(f"missing function end line: {path}:{qualname}")
yield GatedFunction(
path=path,
qualname=qualname,
line_start=node.lineno,
line_count=node.end_lineno - node.lineno + 1,
)
nested_scope = (*scope, node.name, "<locals>")
for child in node.body:
yield from visit(child, nested_scope)
return
if isinstance(node, ast.ClassDef):
class_scope = (*scope, node.name)
for child in node.body:
yield from visit(child, class_scope)
return
if isinstance(node, ast.Lambda):
return
for child in ast.iter_child_nodes(node):
yield from visit(child, scope)
yield from visit(tree, ())
# (path, sha1 of the module text) -> its exact function inventory. Parsing is a
# pure function of that key, and one validation run inventories several trees
# (live tree, staged index, HEAD/parent refs for the pairwise transition) that
# share almost every module blob unchanged: each distinct text is parsed once,
# not once per tree. Bounded: cleared when it outgrows the working set.
_MODULE_FUNCTIONS_CACHE: dict[tuple[str, str], tuple[GatedFunction, ...]] = {}
_MODULE_FUNCTIONS_CACHE_LIMIT = 8192
def _module_functions(module: GatedModule) -> tuple[GatedFunction, ...]:
key = (module.path, hashlib.sha1(module._source_text.encode("utf-8")).hexdigest())
cached = _MODULE_FUNCTIONS_CACHE.get(key)
if cached is not None:
return cached
try:
tree = ast.parse(module._source_text, filename=module.path)
except SyntaxError as exc:
raise ValueError(f"gated Python module has invalid syntax: {module.path}") from exc
functions = tuple(_iter_lexical_functions(tree, module.path))
if len(_MODULE_FUNCTIONS_CACHE) >= _MODULE_FUNCTIONS_CACHE_LIMIT:
_MODULE_FUNCTIONS_CACHE.clear()
_MODULE_FUNCTIONS_CACHE[key] = functions
return functions
def _iter_gated_functions_from_modules(modules: Iterable[GatedModule]) -> Iterator[GatedFunction]:
seen_keys: set[tuple[str, str]] = set()
for module in modules:
posix = pathlib.PurePosixPath(module.path)
if not module.path.endswith(".py"):
continue
if posix.name in FUNCTION_COUNT_EXCLUDED_FILES:
continue
if any(part in _FUNCTION_SKIP_DIR_NAMES for part in posix.parts[:-1]):
continue
for function in _module_functions(module):
key = (function.path, function.qualname)
if key in seen_keys:
raise ValueError(f"duplicate function ratchet key: {key!r}")
seen_keys.add(key)
yield function
def iter_gated_functions(
repo_dir: pathlib.Path,
*,
repo_paths: Iterable[str] | None = None,
) -> Iterator[GatedFunction]:
"""Yield exact Python function debt while preserving the legacy skip scope."""
yield from _iter_gated_functions_from_modules(iter_gated_modules(repo_dir, repo_paths=repo_paths))
def collect_size_ratchet_inventory(
repo_dir: pathlib.Path,
*,
repo_paths: Iterable[str] | None = None,
) -> SizeRatchetInventory:
"""Collect one exact inventory for gates, health, census, and regeneration."""
injected = tuple(repo_paths) if repo_paths is not None else None
modules = tuple(iter_gated_modules(repo_dir, repo_paths=injected))
functions = tuple(_iter_gated_functions_from_modules(modules))
return SizeRatchetInventory(
modules=modules,
functions=functions,
giant_paths=frozenset(item.path for item in modules if item.line_count > MAX_MODULE_LINES),
function_debt=frozenset(
(item.path, item.qualname) for item in functions if item.line_count > MAX_FUNCTION_LINES
),
band_paths=frozenset(
item.path for item in modules if TARGET_MODULE_LINES < item.line_count <= BAND_MODULE_MAX_LINES
),
byte_debt={item.path: item.utf8_bytes for item in modules if item.utf8_bytes > MAX_MODULE_BYTES},
)
@contextlib.contextmanager
def _git_source_snapshot(repo_dir: pathlib.Path, ref: str) -> Iterator[tuple[pathlib.Path, tuple[str, ...]]]:
root = pathlib.Path(repo_dir).resolve()
with tempfile.TemporaryDirectory(prefix="ouroboros-size-ratchet-") as raw_temp:
snapshot = pathlib.Path(raw_temp)
tree = subprocess.run(
["git", "ls-tree", "-rz", "--full-tree", ref],
cwd=root,
check=True,
capture_output=True,
)
entries: list[tuple[str, bytes]] = []
for record in tree.stdout.split(b"\0"):
if not record:
continue
try:
metadata, raw_path = record.split(b"\t", 1)
mode, object_type, object_id = metadata.split(b" ", 2)
except ValueError as exc:
raise ValueError(f"git ls-tree returned a malformed entry at {ref}") from exc
path = _exact_repo_relative_path(raw_path.decode("utf-8"))
if not _is_gated_module_path(path):
continue
if object_type != b"blob" or mode not in {b"100644", b"100755"}:
raise ValueError(f"gated source at {ref} must be a regular file: {path}")
entries.append((path, object_id))
batch = subprocess.run(
["git", "cat-file", "--batch"],
cwd=root,
check=True,
input=b"".join(object_id + b"\n" for _path, object_id in entries),
capture_output=True,
).stdout
cursor = 0
paths: list[str] = []
for path, expected_id in entries:
header_end = batch.find(b"\n", cursor)
if header_end < 0:
raise ValueError(f"git cat-file omitted the header for {path} at {ref}")
header = batch[cursor:header_end].split(b" ")
if len(header) != 3 or header[0] != expected_id or header[1] != b"blob":
raise ValueError(f"git cat-file returned the wrong object for {path} at {ref}")
try:
size = int(header[2])
except ValueError as exc:
raise ValueError(f"git cat-file returned an invalid size for {path} at {ref}") from exc
blob_start = header_end + 1
blob_end = blob_start + size
if blob_end >= len(batch) or batch[blob_end : blob_end + 1] != b"\n":
raise ValueError(f"git cat-file returned a truncated blob for {path} at {ref}")
destination = snapshot.joinpath(*pathlib.PurePosixPath(path).parts)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(batch[blob_start:blob_end])
paths.append(path)
cursor = blob_end + 1
if cursor != len(batch):
raise ValueError(f"git cat-file returned unexpected trailing data at {ref}")
yield snapshot, tuple(paths)
def collect_size_ratchet_inventory_at_ref(repo_dir: pathlib.Path, ref: str) -> SizeRatchetInventory:
"""Collect the canonical inventory from immutable Git blobs at ``ref``."""
with _git_source_snapshot(repo_dir, ref) as (snapshot, paths):
return collect_size_ratchet_inventory(snapshot, repo_paths=paths)
def module_is_grandfathered(path: str) -> bool:
"""Return whether the exact repo-relative path is current module debt."""
return _exact_repo_relative_path(path) in GIANT_PATHS
def function_is_grandfathered(path: str, qualname: str) -> bool:
"""Return whether the exact (repo-relative path, qualname) key is debt."""
return (_exact_repo_relative_path(path), str(qualname)) in FUNCTION_DEBT
def _literal_assignments(text: str) -> dict[str, Any]:
tree = ast.parse(text, filename=SIZE_RATCHET_MANIFEST_PATH)
values: dict[str, Any] = {}
for index, node in enumerate(tree.body):
if (
index == 0
and isinstance(node, ast.Expr)
and isinstance(node.value, ast.Constant)
and isinstance(node.value.value, str)
):
continue
if not isinstance(node, ast.Assign) or len(node.targets) != 1 or not isinstance(node.targets[0], ast.Name):
raise ValueError("size manifest is data-only: expected literal assignments")
name = node.targets[0].id
if name in values:
raise ValueError(f"duplicate size manifest assignment: {name}")
try:
if isinstance(node.value, ast.Dict):
keys = [ast.literal_eval(key) for key in node.value.keys]
if len(keys) != len(set(keys)):
raise ValueError(f"size manifest dict contains duplicate keys: {name}")
values[name] = ast.literal_eval(node.value)
except (TypeError, ValueError) as exc:
if str(exc).startswith("size manifest dict contains duplicate keys"):
raise
raise ValueError(f"size manifest assignment must be literal: {name}") from exc
return values
def parse_size_ratchet_manifest(text: str) -> SizeRatchetManifest:
"""Parse the checked-in data-only manifest without executing it."""
values = _literal_assignments(text)
required = {
"BASELINE_SOURCE_SHA",
"GIANT_PATHS",
"FUNCTION_DEBT",
"BAND_BASELINE_PATHS",
"BAND_PATHS",
"BYTE_BASELINE_DEBT",
"BYTE_DEBT",
}
missing = sorted(required - values.keys())
if missing:
raise ValueError(f"size manifest missing assignments: {', '.join(missing)}")
extra = sorted(values.keys() - required)
if extra:
raise ValueError(f"size manifest has unexpected assignments: {', '.join(extra)}")
def manifest_path(raw: str, label: str) -> str:
try:
return _exact_repo_relative_path(raw)
except ValueError as exc:
raise ValueError(f"{label} path is not canonical POSIX repo-relative: {raw!r}") from exc
def exact_paths(items: Any, label: str) -> frozenset[str]:
if not isinstance(items, tuple):
raise ValueError(f"{label} must be a tuple")
if not all(isinstance(item, str) for item in items):
raise ValueError(f"{label} paths must be strings")
paths = tuple(manifest_path(item, label) for item in items)
if len(paths) != len(set(paths)):
raise ValueError(f"{label} contains duplicate or colliding paths")
for raw, path in zip(items, paths):
if raw != path:
raise ValueError(f"{label} path is not canonical POSIX repo-relative: {raw!r}")
return frozenset(paths)
giant_paths = exact_paths(values["GIANT_PATHS"], "GIANT_PATHS")
band_baseline_paths = exact_paths(values["BAND_BASELINE_PATHS"], "BAND_BASELINE_PATHS")
raw_functions = values["FUNCTION_DEBT"]
if not isinstance(raw_functions, tuple):
raise ValueError("FUNCTION_DEBT must be a tuple")
function_items: list[tuple[str, str]] = []
for item in raw_functions:
if (
not isinstance(item, tuple)
or len(item) != 2
or not isinstance(item[0], str)
or not isinstance(item[1], str)
or not item[1]
):
raise ValueError("FUNCTION_DEBT entries must be (path, nonempty qualname) string pairs")
rel = manifest_path(item[0], "FUNCTION_DEBT")
function_items.append((rel, item[1]))
if len(function_items) != len(set(function_items)):
raise ValueError("FUNCTION_DEBT contains duplicate or colliding keys")
for raw, (path, _qualname) in zip(raw_functions, function_items):
if raw[0] != path:
raise ValueError(f"FUNCTION_DEBT path is not canonical POSIX repo-relative: {raw[0]!r}")
function_debt = frozenset(function_items)
raw_band = values["BAND_PATHS"]
if not isinstance(raw_band, dict):
raise ValueError("BAND_PATHS must be a dict")
band_paths: dict[str, str | None] = {}
for path, rationale in raw_band.items():
if not isinstance(path, str):
raise ValueError("BAND_PATHS keys must be strings")
rel = manifest_path(path, "BAND_PATHS")
if rel in band_paths:
raise ValueError(f"BAND_PATHS contains canonical path collision: {rel}")
if path != rel:
raise ValueError(f"BAND_PATHS path is not canonical POSIX repo-relative: {path!r}")
if rationale is not None and (not isinstance(rationale, str) or not rationale.strip()):
raise ValueError(f"BAND_PATHS rationale must be nonblank or None: {rel}")
band_paths[rel] = rationale
def byte_map(raw: Any, label: str) -> dict[str, int]:
if not isinstance(raw, dict):
raise ValueError(f"{label} must be a dict")
parsed: dict[str, int] = {}
for path, count in raw.items():
if not isinstance(path, str):
raise ValueError(f"{label} keys must be strings")
rel = manifest_path(path, label)
if rel in parsed:
raise ValueError(f"{label} contains canonical path collision: {rel}")
if path != rel:
raise ValueError(f"{label} path is not canonical POSIX repo-relative: {path!r}")
if isinstance(count, bool) or not isinstance(count, int) or count <= MAX_MODULE_BYTES:
raise ValueError(f"{label} must store exact byte counts above {MAX_MODULE_BYTES}: {rel}")
parsed[rel] = count
return parsed
sha = values["BASELINE_SOURCE_SHA"]
if not isinstance(sha, str) or len(sha) != 40 or any(char not in "0123456789abcdef" for char in sha):
raise ValueError("BASELINE_SOURCE_SHA must be a lowercase full commit SHA")
return SizeRatchetManifest(
baseline_source_sha=sha,
giant_paths=giant_paths,
function_debt=function_debt,
band_baseline_paths=band_baseline_paths,
band_paths=band_paths,
byte_baseline_debt=byte_map(values["BYTE_BASELINE_DEBT"], "BYTE_BASELINE_DEBT"),
byte_debt=byte_map(values["BYTE_DEBT"], "BYTE_DEBT"),
)
def load_size_ratchet_manifest(path: pathlib.Path) -> SizeRatchetManifest:
return parse_size_ratchet_manifest(path.read_text(encoding="utf-8"))
_CHECKED_IN_MANIFEST = load_size_ratchet_manifest(pathlib.Path(__file__).with_name("size_ratchet_manifest.py"))
GIANT_PATHS = _CHECKED_IN_MANIFEST.giant_paths
FUNCTION_DEBT = _CHECKED_IN_MANIFEST.function_debt
# Compatibility names remain public during the v7 migration; their keys are now exact.
GRANDFATHERED_OVERSIZED_MODULES = GIANT_PATHS
GRANDFATHERED_OVERSIZED_FUNCTIONS = FUNCTION_DEBT
def validate_manifest_transition(
current: SizeRatchetManifest,
previous: SizeRatchetManifest,
*,
adjacent: bool = True,
) -> list[str]:
"""Validate shrink-only debt and rationale authority against the parent tree.
``adjacent=False`` is the PAIRWISE (interval) form: byte-equality of a
surviving band rationale is only sound between adjacent manifests — across
an interval a path may legally retire and re-enter with a fresh rationale
(every intermediate step green) — so the interval form requires a nonblank
tip rationale instead of equality (Q18-A: interval archaeology is out of
scope; interval FALSE POSITIVES are not)."""
errors: list[str] = []
if current.baseline_source_sha != previous.baseline_source_sha:
errors.append("BASELINE_SOURCE_SHA is immutable")
if current.band_baseline_paths != previous.band_baseline_paths:
errors.append("BAND_BASELINE_PATHS is immutable")
if dict(current.byte_baseline_debt) != dict(previous.byte_baseline_debt):
errors.append("BYTE_BASELINE_DEBT is immutable")
for path in sorted(current.giant_paths - previous.giant_paths):
errors.append(f"new module debt above {MAX_MODULE_LINES} lines: {path}")
added_functions = current.function_debt - previous.function_debt
removed_functions = previous.function_debt - current.function_debt
# A same-qualname relocation — the function left exactly one path and appeared
# at exactly one other in the same transition — moves existing debt, it does
# not create it: the count is unchanged and the ratchet still names the
# function. A fresh >300-line function, a swap onto a different qualname, or
# an ambiguous many-to-one move is still refused.
relocated_functions = {
(path, qualname)
for path, qualname in added_functions
if sum(1 for _p, q in removed_functions if q == qualname) == 1
and sum(1 for _p, q in added_functions if q == qualname) == 1
}
for path, qualname in sorted(added_functions - relocated_functions):
errors.append(f"new function debt above {MAX_FUNCTION_LINES} lines: {path}:{qualname}")
previous_band = set(previous.band_paths)
for path in sorted(set(current.band_paths) - previous_band):
rationale = current.band_paths[path]
if not isinstance(rationale, str) or not rationale.strip():
errors.append(f"new or re-entered 1001-1500 path needs a nonblank rationale: {path}")
for path in sorted(set(current.band_paths) & previous_band):
old = previous.band_paths[path]
new = current.band_paths[path]
if adjacent:
if new != old:
errors.append(f"surviving band rationale is immutable: {path}")
elif old is not None and (new is None or not new.strip()):
# Interval form: a rewrite is legal (retire+re-enter), but DROPPING
# a recorded rationale — to None or to a blank string — across the
# interval launders the justification away.
errors.append(f"band rationale dropped across the interval: {path}")
for path in sorted(set(current.byte_debt) - set(previous.byte_debt)):
errors.append(f"new or re-entered module debt above {MAX_MODULE_BYTES} UTF-8 bytes: {path}")
for path in sorted(set(current.byte_debt) & set(previous.byte_debt)):
if current.byte_debt[path] > previous.byte_debt[path]:
errors.append(f"byte debt grew: {path} {previous.byte_debt[path]} -> {current.byte_debt[path]}")
return errors
def _manifest_inventory_errors(
manifest: SizeRatchetManifest,
inventory: SizeRatchetInventory,
) -> list[str]:
errors: list[str] = []
def compare_set(label: str, live: frozenset[Any], recorded: frozenset[Any]) -> None:
for item in sorted(live - recorded):
errors.append(f"{label} missing live entry: {item!r}")
for item in sorted(recorded - live):
errors.append(f"{label} contains stale entry: {item!r}")
compare_set("GIANT_PATHS", inventory.giant_paths, manifest.giant_paths)
compare_set("FUNCTION_DEBT", inventory.function_debt, manifest.function_debt)
compare_set("BAND_PATHS", inventory.band_paths, frozenset(manifest.band_paths))
if dict(inventory.byte_debt) != dict(manifest.byte_debt):
errors.append(f"BYTE_DEBT differs from live exact counts: live={dict(inventory.byte_debt)!r}")
if len(inventory.functions) > MAX_TOTAL_FUNCTIONS:
errors.append(
f"total function count exceeds {MAX_TOTAL_FUNCTIONS}: {len(inventory.functions)}"
)
return errors
def _bootstrap_baseline_errors(
manifest: SizeRatchetManifest,
inventory: SizeRatchetInventory,
) -> list[str]:
"""A fresh bootstrap seeds its immutable baselines from its own candidate tree."""
errors: list[str] = []
if manifest.band_baseline_paths != inventory.band_paths:
errors.append("BAND_BASELINE_PATHS differs from the bootstrap candidate inventory")
if dict(manifest.byte_baseline_debt) != dict(inventory.byte_debt):
errors.append("BYTE_BASELINE_DEBT differs from the bootstrap candidate inventory")
return errors
def _git_show_manifest(repo_dir: pathlib.Path, ref: str, manifest_path: str) -> str | None:
result = subprocess.run(
["git", "show", f"{ref}:{manifest_path}"],
cwd=repo_dir,
check=False,
capture_output=True,
text=True,
)
return result.stdout if result.returncode == 0 else None
def _staged_manifest_inventory(
repo_dir: pathlib.Path,
index_tree: str,
manifest_path: str,
) -> tuple[str | None, SizeRatchetManifest | None, SizeRatchetInventory | None]:
"""Read one immutable staged projection and label any typed failure."""
staged_text = _git_show_manifest(repo_dir, index_tree, manifest_path)
if staged_text is None:
return None, None, None
try:
staged = parse_size_ratchet_manifest(staged_text)
inventory = collect_size_ratchet_inventory_at_ref(repo_dir, index_tree)
except (OSError, ValueError, subprocess.CalledProcessError) as exc:
raise ValueError(f"staged: {exc}") from exc
return staged_text, staged, inventory
def _commit_parents(repo_dir: pathlib.Path, commit: str) -> list[str]:
"""Exact parent SHAs of ``commit`` in parent order (empty for a root commit)."""
listed = subprocess.run(
["git", "rev-list", "--parents", "-n", "1", commit],
cwd=repo_dir,
check=True,
capture_output=True,
text=True,
).stdout.split()
return listed[1:]
def resolve_committed_manifest_text(
repo_dir: pathlib.Path,
*,
manifest_path: str = SIZE_RATCHET_MANIFEST_PATH,
) -> str | None:
"""Merge-aware committed manifest authority for the checkout's ``HEAD``.
The committed manifest from ``HEAD``'s tree; when that tree lacks it, the
first parent (in parent order) whose tree carries it — a merge that landed
the manifest through EITHER parent line keeps its authority, with no
first-parent history replay. ``None`` means no committed manifest exists on
``HEAD`` or any of its parents: a bootstrap, accepted from the current tree
(interval archaeology is an owner-accepted tradeoff — the official
repository CI enforces the pairwise base-vs-tip transition instead).
"""
root = pathlib.Path(repo_dir).resolve()
head = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=root, check=True, capture_output=True, text=True
).stdout.strip()
text = _git_show_manifest(root, head, manifest_path)
if text is not None:
return text
for parent in _commit_parents(root, head):
text = _git_show_manifest(root, parent, manifest_path)
if text is not None:
return text
return None
def _staged_tree_without_index_lock(root: pathlib.Path) -> str:
"""Return the staged tree id (``git write-tree``) without taking ``index.lock``.
``git write-tree`` rewrites the cache-tree extension into the index and
therefore holds ``.git/index.lock`` with ``LOCK_DIE_ON_ERROR``: any
concurrent ``git status`` / ``git diff`` refresh of the same checkout
(parallel test workers, an agent shell, an editor) makes it die with exit
128 even though the staged content is perfectly readable. The validator only
needs the tree id, so it writes the tree from a private copy of the index
(``GIT_INDEX_FILE``): same staged snapshot, no lock on the live index.
An index without a checkout-level index file (bare/odd layouts) falls back
to the plain command.
"""
index_path = subprocess.run(
["git", "rev-parse", "--git-path", "index"],
cwd=root, check=True, capture_output=True, text=True,
).stdout.strip()
index_file = pathlib.Path(index_path)
if not index_file.is_absolute():
index_file = root / index_file
if not index_file.is_file():
return subprocess.run(
["git", "write-tree"], cwd=root, check=True, capture_output=True, text=True
).stdout.strip()
with tempfile.TemporaryDirectory(prefix="ouroboros-ratchet-index-") as tmp:
private_index = pathlib.Path(tmp) / "index"
private_index.write_bytes(index_file.read_bytes())
env = dict(os.environ)
env["GIT_INDEX_FILE"] = str(private_index)
return subprocess.run(
["git", "write-tree"], cwd=root, check=True, capture_output=True, text=True, env=env
).stdout.strip()
def _validate_manifest_candidate(
root: pathlib.Path,
current_text: str,
*,
manifest_path: str,
include_staged: bool,
) -> list[str]:
"""Shared exactness + merge-aware transition core for live and in-memory candidates."""
current = parse_size_ratchet_manifest(current_text)
inventory = collect_size_ratchet_inventory(root)
errors = _manifest_inventory_errors(current, inventory)
previous_text = resolve_committed_manifest_text(root, manifest_path=manifest_path)
previous = parse_size_ratchet_manifest(previous_text) if previous_text is not None else None
if previous is None:
errors.extend(f"bootstrap: {error}" for error in _bootstrap_baseline_errors(current, inventory))
elif current_text != previous_text:
errors.extend(validate_manifest_transition(current, previous))
if not include_staged:
return errors
head_tree = subprocess.run(
["git", "rev-parse", "HEAD^{tree}"], cwd=root, check=True, capture_output=True, text=True
).stdout.strip()
index_tree = _staged_tree_without_index_lock(root)
if index_tree == head_tree:
return errors
staged_text, staged, staged_inventory = _staged_manifest_inventory(root, index_tree, manifest_path)
if staged_text is None:
if previous is None:
errors.append("staged: size-ratchet bootstrap manifest is missing from the changed index")
else:
errors.append("staged: size-ratchet manifest was removed after bootstrap")
return errors
assert staged is not None and staged_inventory is not None
errors.extend(f"staged: {error}" for error in _manifest_inventory_errors(staged, staged_inventory))
if previous is None:
errors.extend(
f"staged bootstrap: {error}" for error in _bootstrap_baseline_errors(staged, staged_inventory)
)
elif staged_text != previous_text:
errors.extend(f"staged: {error}" for error in validate_manifest_transition(staged, previous))
return errors
def validate_size_ratchet(
repo_dir: pathlib.Path,
*,
manifest_path: str = SIZE_RATCHET_MANIFEST_PATH,
) -> list[str]:
"""Validate live and staged candidates against the merge-aware committed authority.
Enforcement contract: the OFFICIAL repository's CI ``size_ratchet`` lane
BLOCKS on these findings (tip exactness plus the pairwise base-vs-tip
transition); every local surface (default pytest lanes exclude the marker;
``check_worktree_readiness`` and ``codebase_health`` report the findings)
only WARNS. There is no committed-history replay: the previous manifest
resolves from ``HEAD`` or any of its parents, and a checkout with no
committed manifest anywhere bootstraps from its own tree — a locally
evolved fork is never trapped by structural debt it inherited.
"""
root = pathlib.Path(repo_dir).resolve()
current_path = root.joinpath(*pathlib.PurePosixPath(manifest_path).parts)
current_text = current_path.read_text(encoding="utf-8")
return _validate_manifest_candidate(root, current_text, manifest_path=manifest_path, include_staged=True)
def validate_size_ratchet_candidate(
repo_dir: pathlib.Path,
candidate_text: str,
*,
manifest_path: str = SIZE_RATCHET_MANIFEST_PATH,
) -> list[str]:
"""Validate one rendered in-memory manifest BEFORE it is written to disk.
Pure candidate mode for ``scripts/regenerate_size_ratchet.py``: the same
live-tree exactness, bootstrap, and shrink-only transition checks as
``validate_size_ratchet``, with no staged-index checks and no filesystem
write first. The manifest module's own metrics sit far below every debt
threshold today (an observation, not a structural guarantee), which keeps
them out of the debt sets and makes validation against the current live
inventory exact even while the old manifest bytes are still on disk.
"""
root = pathlib.Path(repo_dir).resolve()
return _validate_manifest_candidate(root, candidate_text, manifest_path=manifest_path, include_staged=False)
def validate_size_ratchet_transition_against_base(
repo_dir: pathlib.Path,
base_ref: str | None,
*,
manifest_path: str = SIZE_RATCHET_MANIFEST_PATH,
) -> list[str]:
"""Pairwise (interval-form) shrink-only check of HEAD's manifest vs a base.
CI passes the event base (PR base.sha / push event.before) via
``OURO_SIZE_RATCHET_BASE_REF``. Base semantics — every hole here is a
laundering vector, so degradations are deliberate and narrow: all-zeros
(new-branch/tag push) degrades to HEAD's parent, never skips; a resolvable
base WITHOUT the manifest FAILS (delete-then-rebootstrap laundering; true
first adoption re-runs with a post-adoption base); empty/unresolvable
(force-push loss) degrades to HEAD's parent whose manifest is first
validated against the parent's own tree (fabricated bases are refused);
no parent manifest at all = bootstrap, transition skipped."""
root = pathlib.Path(repo_dir).resolve()
tip_text = _git_show_manifest(root, "HEAD", manifest_path)
if tip_text is None:
return ["pairwise: HEAD does not carry the size-ratchet manifest"]
ref = (base_ref or "").strip()
if ref and not ref.strip("0"):
ref = "" # all-zeros: no event base — fall through to the parent degradation
base_text: str | None = None
validate_parent_tree = False
if ref:
resolved = subprocess.run(
["git", "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"],
cwd=root,
check=False,
capture_output=True,
text=True,
)
if resolved.returncode == 0:
base_text = _git_show_manifest(root, resolved.stdout.strip(), manifest_path)
if base_text is None:
return [
f"pairwise: base {resolved.stdout.strip()[:12]} does not carry the "
"size-ratchet manifest — a deletion inside the interval retires the "
"ratchet's memory; for genuine first adoption re-run with a "
"post-adoption base"
]
if base_text is None:
head = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=root, check=True, capture_output=True, text=True
).stdout.strip()
for parent in _commit_parents(root, head):
base_text = _git_show_manifest(root, parent, manifest_path)
if base_text is not None:
validate_parent_tree = True # the degraded parent base is always tree-verified
parent_ref = parent
break
if base_text is None:
return []
previous = parse_size_ratchet_manifest(base_text)
if validate_parent_tree:
# BEFORE the identical-text shortcut: a fabricated parent manifest that
# pre-declares the tip's debt is byte-identical to the tip's manifest —
# equality is exactly what the laundering constructs.
try:
parent_inventory = collect_size_ratchet_inventory_at_ref(root, parent_ref)
parent_errors = _manifest_inventory_errors(previous, parent_inventory)
except Exception as exc:
parent_errors = [f"pairwise-parent: inventory failed: {type(exc).__name__}: {exc}"]
if parent_errors:
return [
"pairwise: the degraded parent-base manifest does not match the parent's own tree "
"(a fabricated base cannot authorize a transition): " + "; ".join(parent_errors[:3])
]
if base_text == tip_text:
return []
return validate_manifest_transition(
parse_size_ratchet_manifest(tip_text), previous, adjacent=False
)
def _metrics_from_inventory(inventory: SizeRatchetInventory) -> Dict[str, Any]:
modules = inventory.modules
functions = inventory.functions
func_lens = [item.line_count for item in functions]
py_files = [item for item in modules if item.path.endswith(".py")]
js_files = [item for item in modules if item.path.endswith(".js")]
hard_modules = [item for item in modules if item.line_count > MAX_MODULE_LINES]
hard_functions = [item for item in functions if item.line_count > MAX_FUNCTION_LINES]
return {
"total_files": len(modules),
"py_files": len(py_files),
"js_files": len(js_files),
"total_lines": sum(item.line_count for item in modules),
"total_bytes": sum(item.utf8_bytes for item in modules),
"total_functions": len(functions),
"avg_function_length": round(sum(func_lens) / max(1, len(func_lens)), 1) if func_lens else 0,
"max_function_length": max(func_lens) if func_lens else 0,
"largest_files": [
(item.path, item.line_count) for item in sorted(modules, key=lambda x: x.line_count, reverse=True)[:10]
],
"longest_functions": [
(item.path, item.line_start, item.line_count)
for item in sorted(functions, key=lambda x: x.line_count, reverse=True)[:10]
],
"target_drift_functions": [
(item.path, item.line_start, item.line_count)
for item in functions
if item.line_count > TARGET_FUNCTION_LINES
],
"grandfathered_functions": [
(item.path, item.line_start, item.line_count)
for item in hard_functions
if (item.path, item.qualname) in FUNCTION_DEBT
],
"oversized_functions": [
(item.path, item.line_start, item.line_count)
for item in hard_functions
if (item.path, item.qualname) not in FUNCTION_DEBT
],
"target_drift_modules": [
(item.path, item.line_count) for item in modules if item.line_count > TARGET_MODULE_LINES
],
"grandfathered_modules": [(item.path, item.line_count) for item in hard_modules if item.path in GIANT_PATHS],
"oversized_modules": [(item.path, item.line_count) for item in hard_modules if item.path not in GIANT_PATHS],
}
def compute_repo_complexity_metrics(repo_dir: pathlib.Path) -> Dict[str, Any]:
"""Compute health metrics from the same production inventory as the hard gate."""
return _metrics_from_inventory(collect_size_ratchet_inventory(repo_dir))
def compute_complexity_metrics(sections: List[Tuple[str, str]]) -> Dict[str, Any]:
"""Compatibility helper for callers that already hold in-memory sections."""
modules: list[GatedModule] = []
functions: list[GatedFunction] = []
for raw_path, content in sections:
rel = _section_repo_relative_path(raw_path)
if not _is_gated_module_path(rel):
continue
raw = content.encode("utf-8")
modules.append(GatedModule(rel, len(content.splitlines()), len(raw)))
posix = pathlib.PurePosixPath(rel)
if (
rel.endswith(".py")
and posix.name not in FUNCTION_COUNT_EXCLUDED_FILES
and not any(part in _FUNCTION_SKIP_DIR_NAMES for part in posix.parts[:-1])
):
try:
tree = ast.parse(content, filename=rel)
except SyntaxError as exc:
raise ValueError(f"gated Python module has invalid syntax: {rel}") from exc
functions.extend(_iter_lexical_functions(tree, rel))
inventory = SizeRatchetInventory(
modules=tuple(sorted(modules)),
functions=tuple(sorted(functions)),
giant_paths=frozenset(item.path for item in modules if item.line_count > MAX_MODULE_LINES),
function_debt=frozenset(
(item.path, item.qualname) for item in functions if item.line_count > MAX_FUNCTION_LINES
),
band_paths=frozenset(
item.path for item in modules if TARGET_MODULE_LINES < item.line_count <= BAND_MODULE_MAX_LINES
),
byte_debt={item.path: item.utf8_bytes for item in modules if item.utf8_bytes > MAX_MODULE_BYTES},
)
return _metrics_from_inventory(inventory)
def collect_sections(
repo_dir: pathlib.Path,
drive_root: pathlib.Path,
) -> Tuple[List[Tuple[str, str]], Dict[str, Any]]:
"""Compatibility collector backed by the untruncated production inventory."""
del drive_root
root = pathlib.Path(repo_dir).resolve()
modules = tuple(iter_gated_modules(root))
sections = [(f"repo/{module.path}", module._source_text) for module in modules]
return sections, {
"files": len(sections),
"chars": sum(len(content) for _path, content in sections),
"omitted": 0,
}