Skip to content

Commit 2df02fd

Browse files
committed
fix(cli): treat untrusted baseline as empty outside gating mode and fail fast in ci
1 parent 0cab63b commit 2df02fd

5 files changed

Lines changed: 276 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ the HTML report UI, and introduces baseline versioning.
6363

6464
- Baselines are now **versioned** and include a schema version.
6565
- Mismatched baseline versions **fail fast** and require regeneration.
66-
- Baseline loading is now strict: invalid schema/types or oversized baseline files
67-
fail fast to preserve CI integrity.
6866
- Added baseline tamper-evident integrity for v1.3+ files (`generator`, `payload_sha256`)
6967
while keeping legacy baseline behavior as explicit regeneration-required fail-fast.
7068
- Added configurable size guards (`--max-baseline-size-mb`, `--max-cache-size-mb`):
71-
oversized baseline fails fast, oversized cache is ignored with warning.
69+
oversized cache is ignored with warning; oversized/invalid/untrusted baseline is ignored
70+
outside gating mode and treated as empty baseline.
7271
- Behavioral hardening (CLI): baseline validation is now an explicit contract
73-
(legacy/version/schema/python/integrity/size states) with deterministic fail-fast behavior.
72+
(legacy/version/schema/python/integrity/size states). In `--fail-on-new`/`--ci`,
73+
untrusted baseline states fail fast with deterministic exit codes.
7474

7575
**Breaking (CI):** baseline version mismatch now fails hard; CI requires baseline regeneration on upgrade.
7676

codeclone.baseline.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
{
2-
"functions": [
3-
"15f730bde91427ef6cc7878fd98d8f9d2ca3b57e|20-49"
4-
],
2+
"functions": [],
53
"blocks": [],
64
"python_version": "3.13",
75
"baseline_version": "1.3.0",
86
"schema_version": 1,
97
"generator": "codeclone",
10-
"payload_sha256": "ab5bbbe4c098b6aae44202f69c7f26398d4d85ae759ca096418d6fc054571cf1",
11-
"created_at": "2026-02-08T09:06:10+00:00"
8+
"payload_sha256": "92e80b05c857b796bb452de9e62985a1568874da468bc671998133975c94397a",
9+
"created_at": "2026-02-08T09:54:31+00:00"
1210
}

codeclone/cli.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ def _make_console(*, no_color: bool) -> Console:
8181
"integrity_failed",
8282
"too_large",
8383
}
84+
_UNTRUSTED_BASELINE_STATUSES = {
85+
"invalid",
86+
"too_large",
87+
"generator_mismatch",
88+
"integrity_missing",
89+
"integrity_failed",
90+
}
8491

8592

8693
@dataclass(slots=True)
@@ -657,6 +664,7 @@ def process_sequential(with_progress: bool) -> None:
657664
baseline_loaded = False
658665
baseline_status = "missing"
659666
baseline_failure_code: int | None = None
667+
baseline_trusted_for_diff = False
660668

661669
if baseline_exists:
662670
try:
@@ -665,17 +673,22 @@ def process_sequential(with_progress: bool) -> None:
665673
baseline_status = (
666674
e.status if e.status in _VALID_BASELINE_STATUSES else "invalid"
667675
)
668-
if baseline_status == "too_large" or not args.update_baseline:
676+
if not args.update_baseline:
669677
console.print(ui.fmt_invalid_baseline(e))
670-
baseline_failure_code = 2
678+
if args.fail_on_new:
679+
baseline_failure_code = 2
680+
else:
681+
console.print(ui.WARN_BASELINE_IGNORED)
671682
else:
672683
baseline_loaded = True
673684
baseline_status = "ok"
685+
baseline_trusted_for_diff = True
674686
if not args.update_baseline:
675687
if baseline.is_legacy_format():
676688
baseline_status = "legacy"
677689
console.print(ui.fmt_baseline_version_missing(__version__))
678690
baseline_failure_code = 2
691+
baseline_trusted_for_diff = False
679692
else:
680693
if baseline.baseline_version != __version__:
681694
assert baseline.baseline_version is not None
@@ -687,6 +700,7 @@ def process_sequential(with_progress: bool) -> None:
687700
)
688701
)
689702
baseline_failure_code = 2
703+
baseline_trusted_for_diff = False
690704
if baseline.schema_version != BASELINE_SCHEMA_VERSION:
691705
assert baseline.schema_version is not None
692706
if baseline_status == "ok":
@@ -698,6 +712,7 @@ def process_sequential(with_progress: bool) -> None:
698712
)
699713
)
700714
baseline_failure_code = 2
715+
baseline_trusted_for_diff = False
701716
if baseline.python_version:
702717
current_version = _current_python_version()
703718
if baseline.python_version != current_version:
@@ -712,6 +727,7 @@ def process_sequential(with_progress: bool) -> None:
712727
if args.fail_on_new:
713728
console.print(ui.ERR_BASELINE_SAME_PYTHON_REQUIRED)
714729
baseline_failure_code = 2
730+
baseline_trusted_for_diff = False
715731
if baseline_status == "ok":
716732
try:
717733
baseline.verify_integrity()
@@ -723,7 +739,14 @@ def process_sequential(with_progress: bool) -> None:
723739
)
724740
baseline_status = status
725741
console.print(ui.fmt_invalid_baseline(e))
726-
baseline_failure_code = 2
742+
baseline_trusted_for_diff = False
743+
if args.fail_on_new:
744+
baseline_failure_code = 2
745+
else:
746+
console.print(ui.WARN_BASELINE_IGNORED)
747+
if baseline_status in _UNTRUSTED_BASELINE_STATUSES:
748+
baseline_loaded = False
749+
baseline_trusted_for_diff = False
727750
else:
728751
if not args.update_baseline:
729752
console.print(ui.fmt_path(ui.WARN_BASELINE_MISSING, baseline_path))
@@ -742,6 +765,7 @@ def process_sequential(with_progress: bool) -> None:
742765
baseline = new_baseline
743766
baseline_loaded = True
744767
baseline_status = "ok"
768+
baseline_trusted_for_diff = True
745769
# When updating, we don't fail on new, we just saved the new state.
746770
# But we might still want to print the summary.
747771

@@ -755,7 +779,10 @@ def process_sequential(with_progress: bool) -> None:
755779
)
756780

757781
# Diff
758-
new_func, new_block = baseline.diff(func_groups, block_groups)
782+
baseline_for_diff = (
783+
baseline if baseline_trusted_for_diff else Baseline(baseline_path)
784+
)
785+
new_func, new_block = baseline_for_diff.diff(func_groups, block_groups)
759786
new_clones_count = len(new_func) + len(new_block)
760787

761788
_print_summary(

codeclone/ui_messages.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@
102102
"[dim]Comparing against an empty baseline. "
103103
"Use --update-baseline to create it.[/dim]"
104104
)
105+
WARN_BASELINE_IGNORED = (
106+
"[warning]Baseline is not trusted for this run and will be ignored.[/warning]\n"
107+
"[dim]Comparison will proceed against an empty baseline.[/dim]"
108+
)
105109
SUCCESS_BASELINE_UPDATED = "✔ Baseline updated: {path}"
106110

107111
FAIL_NEW_TITLE = "[error]FAILED: New code clones detected.[/error]"

0 commit comments

Comments
 (0)