Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7627a63
fix: exclude vendored third-party code from the native lane
saiganakato Jun 21, 2026
3b92066
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
ce8c295
refactor: derive borrowed-analyzer skip sets from the generated-path …
saiganakato Jun 21, 2026
48d6fd8
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
e0d57cb
test: lock diff-mode tolerance of deleted files in the changed set
saiganakato Jun 21, 2026
d6633da
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
f2ef0b1
test: lock the baseline lifecycle across a real edit cycle
saiganakato Jun 21, 2026
e8e4625
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
ab19716
fix: borrowed-analyzer schema drift degrades to parse-failure, not a …
saiganakato Jun 21, 2026
e0a2971
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
0c925df
feat: surface the non-exhaustiveness disclosure in the human PR summary
saiganakato Jun 21, 2026
a3f79c1
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
1275d5c
feat: aci emit-baseline — generate an operations baseline from a scan…
saiganakato Jun 21, 2026
b512302
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
7863025
fix: stop suppressing native signals ruff doesn't actually emit (dogf…
saiganakato Jun 21, 2026
942c3ac
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
1a3bfee
refactor: extract the report helpers ACI flagged as duplicated in its…
saiganakato Jun 21, 2026
cb6672d
Merge branch 'main' into claude/dreamy-brattain-c0180a
saiganakato Jun 21, 2026
71300e6
fix: resolve self-scan CI-02 findings; baseline accepted detector com…
saiganakato Jun 21, 2026
fb14de9
chore: add .mypy_cache to .gitignore
saiganakato Jun 22, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__/
.env.*
.ruff_cache/
.pytest_cache/
.mypy_cache/
.coverage
.coverage.*
htmlcov/
Expand Down
16 changes: 16 additions & 0 deletions aci.self-scan-operations.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ACI baseline -- generated from a scan report by `aci emit-baseline`.
# Each entry accepts a finding as pre-existing; future scans report only
# NEW findings. Identity is the fingerprint (stable across line shifts), so
# no line numbers are stored here. Remove an entry once its finding is
# fixed -- ACI then reports it as resolved on the next scan.
[baseline]
entries = [
# _iter_pep621_dependency_specs: depth-5 nesting mirrors the literal depth of
# the PEP 621 TOML schema (project → optional-dependencies → group → item).
# Splitting the traversal across functions would fragment a single-purpose parse.
{ fingerprint = "edcbbddad0c4eb15", ci_id = "CI-02", target_file = "detectors/ci_14.py" },
# _collect_tainted_names: fixpoint dataflow over an AST walk has an irreducible
# structure: while-convergence × for-walk × if-dispatch × for-targets.
# The nesting is the algorithm — not incidental tangling.
{ fingerprint = "fea6bd94c9a930d5", ci_id = "CI-02", target_file = "detectors/ci_14_taint.py" },
]
83 changes: 38 additions & 45 deletions shared/python/aci_github_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,30 @@
"""GitHub-friendly markdown summary emitter for ACI reports."""
from __future__ import annotations

from typing import Callable

try:
from .aci_report_helpers import report_map as _report_map
except ImportError: # pragma: no cover - direct script/module import path
from aci_report_helpers import report_map as _report_map # type: ignore[no-redef]


def _append_list_section(
lines: list[str],
items: object,
header: str,
row: Callable[[dict[str, object]], str],
) -> None:
if not isinstance(items, list) or not items:
return
lines.append(f"### {header}")
lines.append("")
for item in items:
if isinstance(item, dict):
lines.append(row(item))
lines.append("")


def build_github_summary_markdown(report: dict[str, object]) -> str:
gate = _report_map(report.get("gate"))
summary = _report_map(report.get("summary"))
Expand Down Expand Up @@ -41,51 +59,26 @@ def build_github_summary_markdown(report: dict[str, object]) -> str:
if advisory_headline:
lines.append(str(advisory_headline))
lines.append("")
top_files = review_brief.get("top_files", [])
if isinstance(top_files, list) and top_files:
lines.append("### Hottest Files")
lines.append("")
for item in top_files:
if not isinstance(item, dict):
continue
lines.append(f"- `{item.get('name', '')}`: {item.get('count', 0)} finding(s)")
lines.append("")
top_signals = review_brief.get("top_signals", [])
if isinstance(top_signals, list) and top_signals:
lines.append("### Top Signals")
lines.append("")
for item in top_signals:
if not isinstance(item, dict):
continue
lines.append(f"- `{item.get('name', '')}`: {item.get('count', 0)}")
lines.append("")
advisory_scope_classes = review_brief.get("advisory_scope_classes", [])
if isinstance(advisory_scope_classes, list) and advisory_scope_classes:
lines.append("### Advisory Scope Classes")
lines.append("")
for item in advisory_scope_classes:
if not isinstance(item, dict):
continue
lines.append(f"- `{item.get('name', '')}`: {item.get('count', 0)}")
lines.append("")
analyzer_failures = review_brief.get("analyzer_failures", [])
if isinstance(analyzer_failures, list) and analyzer_failures:
lines.append("### Analyzer Failures")
lines.append("")
for item in analyzer_failures:
if not isinstance(item, dict):
continue
lines.append(f"- `{item.get('analyzer_id', '')}`: `{item.get('runtime_state', '')}`")
lines.append("")
analyzer_availability_notes = review_brief.get("analyzer_availability_notes", [])
if isinstance(analyzer_availability_notes, list) and analyzer_availability_notes:
lines.append("### Analyzer Availability Notes")
lines.append("")
for item in analyzer_availability_notes:
if not isinstance(item, dict):
continue
lines.append(f"- `{item.get('analyzer_id', '')}`: `{item.get('runtime_state', '')}`")
lines.append("")
_append_list_section(
lines, review_brief.get("top_files"), "Hottest Files",
lambda i: f"- `{i.get('name', '')}`: {i.get('count', 0)} finding(s)",
)
_append_list_section(
lines, review_brief.get("top_signals"), "Top Signals",
lambda i: f"- `{i.get('name', '')}`: {i.get('count', 0)}",
)
_append_list_section(
lines, review_brief.get("advisory_scope_classes"), "Advisory Scope Classes",
lambda i: f"- `{i.get('name', '')}`: {i.get('count', 0)}",
)
_append_list_section(
lines, review_brief.get("analyzer_failures"), "Analyzer Failures",
lambda i: f"- `{i.get('analyzer_id', '')}`: `{i.get('runtime_state', '')}`",
)
_append_list_section(
lines, review_brief.get("analyzer_availability_notes"), "Analyzer Availability Notes",
lambda i: f"- `{i.get('analyzer_id', '')}`: `{i.get('runtime_state', '')}`",
)
# The non-exhaustiveness disclosure must travel with the human-facing summary,
# not just the JSON report: this PR summary is where a reviewer forms the
# judgement "ACI passed, the code is clean." A pass with zero findings is
Expand Down
105 changes: 47 additions & 58 deletions shared/python/aci_report_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,37 @@ def _build_residuals(findings: list[dict[str, object]]) -> list[dict[str, object
return rows


def _gate_fail_reasons(
*,
blockers: list[dict[str, object]],
new_findings: list[dict[str, object]],
unreviewed: list[dict[str, object]],
analyzer_failures: list[dict[str, object]],
fail_on_new_findings: bool,
fail_on_unreviewed_review_required: bool,
fail_on_analyzer_errors: bool,
) -> tuple[list[str], list[dict[str, object]]]:
reasons: list[str] = []
details: list[dict[str, object]] = []
if blockers:
reasons.append("severity-threshold")
details.append({"reason": "severity-threshold", "count": len(blockers),
"finding_ids": [str(i.get("finding_id") or "") for i in blockers]})
if fail_on_new_findings and new_findings:
reasons.append("new-findings-present")
details.append({"reason": "new-findings-present", "count": len(new_findings),
"finding_ids": [str(i.get("finding_id") or "") for i in new_findings]})
if fail_on_unreviewed_review_required and unreviewed:
reasons.append("unreviewed-review-required")
details.append({"reason": "unreviewed-review-required", "count": len(unreviewed),
"finding_ids": [str(i.get("finding_id") or "") for i in unreviewed]})
if fail_on_analyzer_errors and analyzer_failures:
reasons.append("analyzer-runtime-error")
details.append({"reason": "analyzer-runtime-error", "count": len(analyzer_failures),
"analyzers": [str(i.get("analyzer_id") or "") for i in analyzer_failures]})
return reasons, details


def _build_gate(
findings: list[dict[str, object]],
report: dict[str, object],
Expand All @@ -288,24 +319,18 @@ def _build_gate(
source_gate = _report_map(report.get("gate"))
severity_threshold = str(source_gate.get("severity_threshold") or SEVERITY_HIGH)
threshold_rank = _SEVERITY_RANK.get(severity_threshold, _SEVERITY_RANK[SEVERITY_HIGH])
blocking_severities = [
severity for severity, rank in _SEVERITY_RANK.items() if rank >= threshold_rank
]
fail_on_new_findings = bool(source_gate.get("fail_on_new_findings", False))
fail_on_unreviewed_review_required = bool(
source_gate.get("fail_on_unreviewed_review_required", False)
)
fail_on_analyzer_errors = bool(source_gate.get("fail_on_analyzer_errors", False))
gated_findings = [
item for item in findings if _row_scope_class(item) in gate_scope_classes
]
blocking_severities = [s for s, r in _SEVERITY_RANK.items() if r >= threshold_rank]
fail_on_new = bool(source_gate.get("fail_on_new_findings", False))
fail_on_unreviewed = bool(source_gate.get("fail_on_unreviewed_review_required", False))
fail_on_errors = bool(source_gate.get("fail_on_analyzer_errors", False))
gated = [item for item in findings if _row_scope_class(item) in gate_scope_classes]
new_findings = [
item for item in gated_findings
item for item in gated
if str(item.get("baseline_status") or "") == "new"
and str(item.get("waiver_status") or "none") == "none"
]
unreviewed = [
item for item in gated_findings
item for item in gated
if str(item.get("owner_lane") or "") == LANE_HUMAN_JUDGMENT
and str(item.get("baseline_status") or "") == "new"
and str(item.get("waiver_status") or "none") == "none"
Expand All @@ -315,48 +340,12 @@ def _build_gate(
item for item in analyzer_runs
if str(item.get("runtime_state") or "") not in _NON_FAILING_ANALYZER_RUNTIME_STATES
]
reasons: list[str] = []
if blockers:
reasons.append("severity-threshold")
if fail_on_new_findings and new_findings:
reasons.append("new-findings-present")
if fail_on_unreviewed_review_required and unreviewed:
reasons.append("unreviewed-review-required")
if fail_on_analyzer_errors and analyzer_failures:
reasons.append("analyzer-runtime-error")
reason_details: list[dict[str, object]] = []
if blockers:
reason_details.append(
{
"reason": "severity-threshold",
"count": len(blockers),
"finding_ids": [str(item.get("finding_id") or "") for item in blockers],
}
)
if fail_on_new_findings and new_findings:
reason_details.append(
{
"reason": "new-findings-present",
"count": len(new_findings),
"finding_ids": [str(item.get("finding_id") or "") for item in new_findings],
}
)
if fail_on_unreviewed_review_required and unreviewed:
reason_details.append(
{
"reason": "unreviewed-review-required",
"count": len(unreviewed),
"finding_ids": [str(item.get("finding_id") or "") for item in unreviewed],
}
)
if fail_on_analyzer_errors and analyzer_failures:
reason_details.append(
{
"reason": "analyzer-runtime-error",
"count": len(analyzer_failures),
"analyzers": [str(item.get("analyzer_id") or "") for item in analyzer_failures],
}
)
reasons, reason_details = _gate_fail_reasons(
blockers=blockers, new_findings=new_findings, unreviewed=unreviewed,
analyzer_failures=analyzer_failures, fail_on_new_findings=fail_on_new,
fail_on_unreviewed_review_required=fail_on_unreviewed,
fail_on_analyzer_errors=fail_on_errors,
)
return {
"decision": "fail" if reasons else "pass",
"blocking_severities": blocking_severities,
Expand All @@ -366,9 +355,9 @@ def _build_gate(
"reasons": reasons,
"reason_details": reason_details,
"severity_threshold": severity_threshold,
"fail_on_new_findings": fail_on_new_findings,
"fail_on_unreviewed_review_required": fail_on_unreviewed_review_required,
"fail_on_analyzer_errors": fail_on_analyzer_errors,
"fail_on_new_findings": fail_on_new,
"fail_on_unreviewed_review_required": fail_on_unreviewed,
"fail_on_analyzer_errors": fail_on_errors,
}


Expand Down
Loading