From 30e4fa55591578e3314cc15edec688bc925b3ffd Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:26:44 +0100 Subject: [PATCH] Read staged governance inputs from the Git index The commit guard read tracked status and registry files from the working tree, allowing unstaged edits to change its decision about staged content. Use the shared strict index reader and fail when required inputs are absent. Regression tests use real Git indexes and cover divergent, malformed and missing working-tree files plus missing index entries. --- .../staged_commit_shape_guard.py | 6 +- .../staged_commit_shape_guard_test.py | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/scripts/release_control/staged_commit_shape_guard.py b/scripts/release_control/staged_commit_shape_guard.py index bb5211cc63..d2614c7a90 100644 --- a/scripts/release_control/staged_commit_shape_guard.py +++ b/scripts/release_control/staged_commit_shape_guard.py @@ -58,8 +58,7 @@ def load_head_repo_json(path: str) -> dict | None: def load_staged_status_payload() -> dict: - # Internal governance files are not git-tracked; always read from filesystem. - return load_repo_json(STATUS_PATH, staged=False) + return load_repo_json(STATUS_PATH, staged=True, strict_staged=True) def load_head_status_payload() -> dict | None: @@ -67,8 +66,7 @@ def load_head_status_payload() -> dict | None: def load_staged_registry_payload() -> dict: - # Internal governance files are not git-tracked; always read from filesystem. - return load_repo_json(REGISTRY_PATH, staged=False) + return load_repo_json(REGISTRY_PATH, staged=True, strict_staged=True) def _is_runtime_surface_path(path: str) -> bool: diff --git a/scripts/release_control/staged_commit_shape_guard_test.py b/scripts/release_control/staged_commit_shape_guard_test.py index 3bf53b070b..7036f42b86 100644 --- a/scripts/release_control/staged_commit_shape_guard_test.py +++ b/scripts/release_control/staged_commit_shape_guard_test.py @@ -1,19 +1,84 @@ import io +import json import os +import subprocess +import tempfile import unittest +from pathlib import Path from contextlib import redirect_stderr, redirect_stdout from unittest.mock import patch +from repo_file_io import strip_local_git_env from staged_commit_shape_guard import ( + REGISTRY_PATH, + STATUS_PATH, CONTRACT_NEUTRAL_OVERRIDE_ENV, advanced_lane_ids, format_combined_errors, lane_progress_shape_errors, + load_staged_registry_payload, + load_staged_status_payload, main, staged_promotion_proof_errors, ) +class StagedGovernanceInputTest(unittest.TestCase): + def git(self, repo_root: Path, *args: str) -> None: + env = os.environ.copy() + strip_local_git_env(env) + subprocess.run( + ["git", *args], + cwd=repo_root, + env=env, + check=True, + capture_output=True, + text=True, + ) + + def test_staged_inputs_ignore_unstaged_edits_and_missing_worktree_files(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + repo_root = Path(tmpdir) + self.git(repo_root, "init", "--initial-branch=main") + loaders = ( + (STATUS_PATH, load_staged_status_payload), + (REGISTRY_PATH, load_staged_registry_payload), + ) + expected = {"fixture_source": "index"} + for rel, _ in loaders: + path = repo_root / rel + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(expected) + "\n", encoding="utf-8") + self.git(repo_root, "add", "--", *(rel for rel, _ in loaders)) + + with patch("repo_file_io.REPO_ROOT", repo_root): + for rel, loader in loaders: + path = repo_root / rel + for content in ('{"fixture_source": "working-tree"}', "invalid json"): + with self.subTest(path=rel, unstaged_content=content): + path.write_text(content + "\n", encoding="utf-8") + self.assertEqual(loader(), expected) + with self.subTest(path=rel, worktree_missing=True): + path.unlink() + self.assertEqual(loader(), expected) + + def test_missing_index_inputs_fail_closed_with_worktree_files_present(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + repo_root = Path(tmpdir) + self.git(repo_root, "init", "--initial-branch=main") + with patch("repo_file_io.REPO_ROOT", repo_root): + for rel, loader in ( + (STATUS_PATH, load_staged_status_payload), + (REGISTRY_PATH, load_staged_registry_payload), + ): + with self.subTest(path=rel): + path = repo_root / rel + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text('{"fixture_source": "working-tree"}\n', encoding="utf-8") + with self.assertRaises(FileNotFoundError): + loader() + + class StagedCommitShapeGuardTest(unittest.TestCase): def test_advanced_lane_ids_detects_score_or_completion_progress(self) -> None: previous = {