From a7b262149dddb0e7a041c7fbdec87a56b4c61498 Mon Sep 17 00:00:00 2001 From: lostlight530 <13957086206@163.com> Date: Mon, 26 Oct 2026 07:41:47 +0800 Subject: [PATCH 1/3] test: reject maintenance report collision with inspected inputs --- tests/test_tool_cli.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_tool_cli.py b/tests/test_tool_cli.py index 32a52db..fc12bca 100644 --- a/tests/test_tool_cli.py +++ b/tests/test_tool_cli.py @@ -288,5 +288,60 @@ def test_handoff_cli_does_not_clobber_legacy_temp_name(self): self.assertEqual(source.read_bytes(), original) + + def test_maintenance_report_output_cannot_overwrite_inspected_files(self): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + (root / "maintenance").mkdir() + config = root / "maintenance" / "cadence.yaml" + config.write_text(json.dumps({ + "project_profile_prefix": "sci-render-kit/", + "canonical_paths": [], + "scan_paths": [], + "forbidden_governance_paths": [], + "history_patterns": [], + "cadences": {"daily": {"calibration_max_age_days": 0}}, + }), encoding="utf-8") + manifest = root / "MANIFEST.yaml" + manifest.write_text('calibrated: "2026-10-01"\n', encoding="utf-8") + existing = root / "research-notes.json" + existing.write_text('{"private":"must survive"}', encoding="utf-8") + + command = [ + sys.executable, str(ROOT / "core" / "maintenance_cadence.py"), + "daily", "--root", str(root), "--config", "maintenance/cadence.yaml", + "--as-of", "2026-10-26", + ] + for protected in (config, manifest, existing): + with self.subTest(protected=protected.name): + original = protected.read_bytes() + attempt = subprocess.run( + command + ["--output", str(protected)], + cwd=ROOT, capture_output=True, text=True, + ) + self.assertNotEqual(attempt.returncode, 0) + self.assertIn("not an existing maintenance report", attempt.stderr) + self.assertEqual(protected.read_bytes(), original) + + report = root / "maintenance-report.json" + for _ in range(2): + allowed = subprocess.run( + command + ["--output", str(report)], + cwd=ROOT, capture_output=True, text=True, + ) + self.assertEqual(allowed.returncode, 0, allowed.stderr) + self.assertEqual(json.loads(report.read_text(encoding="utf-8"))["profile"], + "sci-render-kit/maintenance-report") + self.assertEqual(config.read_text(encoding="utf-8"), + json.dumps({ + "project_profile_prefix": "sci-render-kit/", + "canonical_paths": [], + "scan_paths": [], + "forbidden_governance_paths": [], + "history_patterns": [], + "cadences": {"daily": {"calibration_max_age_days": 0}}, + })) + + if __name__ == "__main__": unittest.main() From c82f664726ce4ed0be71aa1239ddd86c941bacfe Mon Sep 17 00:00:00 2001 From: lostlight530 <13957086206@163.com> Date: Mon, 26 Oct 2026 07:42:13 +0800 Subject: [PATCH 2/3] fix: fail closed on maintenance report output collisions --- core/maintenance_cadence.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/maintenance_cadence.py b/core/maintenance_cadence.py index 12ef840..3873cd4 100644 --- a/core/maintenance_cadence.py +++ b/core/maintenance_cadence.py @@ -390,6 +390,23 @@ def main(argv: Optional[list[str]] = None) -> int: config_path = root / config_path output_path = Path(args.output).resolve() if args.output else None + # A report must not overwrite an inspected source, Manifest, or cadence + # configuration. Repeated runs may replace their own prior report only. + if output_path is not None and output_path.exists(): + try: + previous = json.loads(output_path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError): + parser.error("--output target is not an existing maintenance report") + if not ( + isinstance(previous, dict) + and previous.get("profile") == PROFILE + and previous.get("cadence") in {"daily", "weekly", "monthly"} + and isinstance(previous.get("maintenance_boundaries"), dict) + and isinstance(previous.get("configuration_file_sha256"), str) + and previous["configuration_file_sha256"].startswith("sha256:") + ): + parser.error("--output target is not an existing maintenance report") + report = build_report(root=root, config_path=config_path, cadence=args.cadence, as_of=as_of, report_output_path=output_path) payload = json.dumps(report, ensure_ascii=False, indent=2) + "\n" if output_path is not None: From 4cb237f805ddcab91fad21a308e7e2a5a672643e Mon Sep 17 00:00:00 2001 From: lostlight530 <13957086206@163.com> Date: Mon, 26 Oct 2026 07:42:46 +0800 Subject: [PATCH 3/3] docs: bound maintenance report regeneration to prior reports --- docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md b/docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md index fe75770..3202588 100644 --- a/docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md +++ b/docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md @@ -153,6 +153,8 @@ Optional report output: python core/maintenance_cadence.py daily --as-of YYYY-MM-DD --output output/communication-maintenance-YYYY-MM-DD.json ``` +An existing `--output` destination can be replaced only when its JSON already has this repository's `maintenance-report` profile and expected report structure. Existing configuration, Manifest, research inputs and unrelated files are rejected without mutation. A missing destination may be created. This preserves explicit report regeneration without treating arbitrary caller-selected input files as expendable report outputs. + The scanner enforces declared repository-local structural scope. It does not render figures, inspect pixels, call external services, run tests, validate statistics, certify WCAG conformance, predict publisher acceptance, establish independent reproduction, inspect GitHub PR ownership, or validate historical Jules claims. ### Optional Tool CLI smoke workflow boundary