diff --git a/scripts/ops/report_source_deploy_runtime_truth.py b/scripts/ops/report_source_deploy_runtime_truth.py index 4fdaf55..2f6acf4 100644 --- a/scripts/ops/report_source_deploy_runtime_truth.py +++ b/scripts/ops/report_source_deploy_runtime_truth.py @@ -82,8 +82,37 @@ def collect_source_control( root: Path, gitea_remote: str, tracked_files: Iterable[str], + source_override: dict[str, Any] | None = None, runner: CommandRunner = run_command, ) -> dict[str, Any]: + if source_override: + working_tree_version = source_override.get("working_tree_config_version") or read_working_tree_config_version(root) + head_config_version = source_override.get("head_config_version") or working_tree_version + tracked_file_status = [] if source_override.get("tracked_files_committed") else [ + "override: tracked deployment files not confirmed committed" + ] + return { + "truth_source": "Gitea", + "source_mode": "override_for_no_git_deployment_tree", + "local": { + "branch": source_override.get("branch") or "main", + "head": source_override["head"], + "working_tree_config_version": working_tree_version, + "head_config_version": head_config_version, + "tracked_file_status": tracked_file_status, + }, + "origin": { + "remote": source_override.get("origin_remote") or "https://gitea.wooo.work/wooo/ewoooc.git", + "main": source_override["origin_main"], + "dev": source_override["origin_dev"], + }, + "gitea_ssh": { + "remote": gitea_remote, + "main": source_override["gitea_main"], + "dev": source_override["gitea_dev"], + }, + } + origin_refs = parse_ls_remote( _run_git(["ls-remote", "origin", "refs/heads/main", "refs/heads/dev"], root, runner) ) @@ -301,13 +330,20 @@ def build_report( gitea_remote: str = DEFAULT_GITEA_REMOTE, tracked_files: Iterable[str] = DEFAULT_TRACKED_FILES, container_name: str | None = None, + source_override: dict[str, Any] | None = None, runner: CommandRunner = run_command, health_fetcher: HealthFetcher = fetch_json, ) -> dict[str, Any]: tracked_file_tuple = tuple(tracked_files) report = { "policy": "p4_source_deployment_runtime_truth_v1", - "source_control": collect_source_control(root, gitea_remote, tracked_file_tuple, runner), + "source_control": collect_source_control( + root, + gitea_remote, + tracked_file_tuple, + source_override, + runner, + ), "deployment": collect_deployment_files(root, tracked_file_tuple), "runtime": collect_runtime(health_url, timeout, root, container_name, runner, health_fetcher), "safety_gates": safety_gates(), @@ -359,10 +395,51 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--gitea-remote", default=DEFAULT_GITEA_REMOTE) parser.add_argument("--container-name") parser.add_argument("--tracked-file", action="append", dest="tracked_files") + parser.add_argument("--source-head", help="Use explicit source HEAD when deployment tree has no .git") + parser.add_argument("--source-branch", default="main") + parser.add_argument("--origin-main") + parser.add_argument("--origin-dev") + parser.add_argument("--gitea-main") + parser.add_argument("--gitea-dev") + parser.add_argument("--origin-remote", default="https://gitea.wooo.work/wooo/ewoooc.git") + parser.add_argument("--head-config-version") + parser.add_argument("--working-tree-config-version") + parser.add_argument( + "--tracked-files-committed", + action="store_true", + help="Confirm the explicit source HEAD already contains the tracked deployment files.", + ) parser.add_argument("--json", action="store_true", help="Print machine-readable report") args = parser.parse_args(argv) tracked_files = tuple(args.tracked_files) if args.tracked_files else DEFAULT_TRACKED_FILES + source_override = None + if args.source_head: + required_overrides = { + "origin_main": args.origin_main, + "origin_dev": args.origin_dev, + "gitea_main": args.gitea_main, + "gitea_dev": args.gitea_dev, + } + missing = [name for name, value in required_overrides.items() if not value] + if missing: + print( + "source_deploy_runtime_truth:\n- result: BLOCKED\n- blocker: missing source override fields: " + + ", ".join(missing) + ) + return 2 + source_override = { + "branch": args.source_branch, + "head": args.source_head, + "origin_main": args.origin_main, + "origin_dev": args.origin_dev, + "gitea_main": args.gitea_main, + "gitea_dev": args.gitea_dev, + "origin_remote": args.origin_remote, + "head_config_version": args.head_config_version, + "working_tree_config_version": args.working_tree_config_version, + "tracked_files_committed": args.tracked_files_committed, + } try: report = build_report( @@ -372,6 +449,7 @@ def main(argv: list[str] | None = None) -> int: gitea_remote=args.gitea_remote, tracked_files=tracked_files, container_name=args.container_name, + source_override=source_override, ) except ( OSError, diff --git a/tests/test_source_deploy_runtime_truth_report.py b/tests/test_source_deploy_runtime_truth_report.py index 97fc4ef..f5bead5 100644 --- a/tests/test_source_deploy_runtime_truth_report.py +++ b/tests/test_source_deploy_runtime_truth_report.py @@ -169,3 +169,54 @@ def test_text_output_exposes_source_deployment_and_runtime_layers(tmp_path): assert "production_health: healthy postgresql V10.725" in text assert "deployment_files_hashed: 2" in text assert "truth_layers_separated: true" in text + + +def test_report_can_use_source_overrides_for_no_git_deployment_tree(tmp_path): + source_root = _write_source_root(tmp_path) + + payload = report.build_report( + root=source_root, + tracked_files=("config.py", "proof.txt"), + source_override={ + "branch": "main", + "head": LOCAL_HEAD, + "origin_main": LOCAL_HEAD, + "origin_dev": LOCAL_HEAD, + "gitea_main": LOCAL_HEAD, + "gitea_dev": LOCAL_HEAD, + "head_config_version": "V10.725", + "tracked_files_committed": True, + }, + runner=lambda args, cwd: (_ for _ in ()).throw(AssertionError("git should not run")), + health_fetcher=_health(), + ) + + assert payload["result"] == "PASS" + assert payload["source_control"]["source_mode"] == "override_for_no_git_deployment_tree" + assert payload["summary"]["source_control_ok"] is True + assert payload["summary"]["tracked_files_committed"] is True + + +def test_source_override_still_requires_committed_file_confirmation(tmp_path): + source_root = _write_source_root(tmp_path) + + payload = report.build_report( + root=source_root, + tracked_files=("config.py", "proof.txt"), + source_override={ + "branch": "main", + "head": LOCAL_HEAD, + "origin_main": LOCAL_HEAD, + "origin_dev": LOCAL_HEAD, + "gitea_main": LOCAL_HEAD, + "gitea_dev": LOCAL_HEAD, + "head_config_version": "V10.725", + "tracked_files_committed": False, + }, + runner=lambda args, cwd: (_ for _ in ()).throw(AssertionError("git should not run")), + health_fetcher=_health(), + ) + + assert payload["result"] == "BLOCKED" + assert payload["summary"]["tracked_files_committed"] is False + assert any("uncommitted source-control changes" in error for error in payload["errors"])