Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions core/maintenance_cadence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions docs/03-maintenance-and-audit/MAINTENANCE_CADENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions tests/test_tool_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading