Skip to content

Commit 40c8670

Browse files
authored
Merge pull request #802 from efegokdemir/codex/skill-800-scope-exclusion-accounting
fix(ledger): exclude scope boundaries from analyzer work
2 parents 3e805d1 + 28ecaf3 commit 40c8670

3 files changed

Lines changed: 77 additions & 2 deletions

File tree

‎src/skillspector/inspection_ledger.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,10 @@ def analyzer_status_event(
548548
def analyzer_status_for_events(
549549
analyzer_id: str, events: Iterable[InspectionLedgerEvent]
550550
) -> AnalyzerStatusEvent:
551-
"""Summarize an analyzer's terminal work without exposing event payloads."""
552-
terminal_events = list(events)
551+
"""Summarize analyzer work; scope-boundary records remain separate exclusions."""
552+
terminal_events = [
553+
event for event in events if event.get("record_type") != LedgerRecordType.SCOPE_BOUNDARY
554+
]
553555
if not terminal_events:
554556
return analyzer_status_event(
555557
analyzer_id=analyzer_id,

‎tests/nodes/test_finalize_inspection_ledger.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
LedgerReason,
2323
LedgerRecordType,
2424
analyzer_status_event,
25+
analyzer_status_for_events,
2526
finalize_ledger,
2627
guard_analyzer_node,
2728
inspection_work_id,
@@ -125,6 +126,55 @@ def test_completed_work_is_covered_and_resolves_emitted_finding_ids() -> None:
125126
assert effective_ids == [finding.finding_id]
126127

127128

129+
def test_out_of_scope_boundaries_do_not_count_as_analyzer_failures() -> None:
130+
"""Non-fatal scope boundaries are not planned work for an analyzer."""
131+
analyzer_id = "static_patterns_prompt_injection"
132+
inspected = ledger_event(
133+
outcome=LedgerOutcome.COMPLETED,
134+
phase="static",
135+
analyzer_id=analyzer_id,
136+
path="scripts/run.py",
137+
)
138+
excluded = ledger_event(
139+
outcome=LedgerOutcome.OUT_OF_SCOPE,
140+
record_type=LedgerRecordType.SCOPE_BOUNDARY,
141+
phase="static",
142+
analyzer_id=analyzer_id,
143+
path="assets/font.ttf",
144+
reason=LedgerReason.BINARY_CONTENT,
145+
)
146+
147+
completeness, _ = finalize_ledger(
148+
{
149+
"components": ["scripts/run.py", "assets/font.ttf"],
150+
"findings": [],
151+
"inspection_ledger": [inspected, excluded],
152+
"analyzer_status_events": [
153+
analyzer_status_for_events(analyzer_id, [inspected, excluded])
154+
],
155+
"artifact_inventory": [
156+
{"path": "scripts/run.py", "disposition": "complete"},
157+
{
158+
"path": "assets/font.ttf",
159+
"disposition": "out_of_scope",
160+
"content_kind": "binary",
161+
},
162+
],
163+
}
164+
)
165+
166+
status = next(
167+
row for row in completeness["analyzer_statuses"] if row["analyzer_id"] == analyzer_id
168+
)
169+
assert status["status"] == "completed"
170+
assert status["planned_work"] == 1
171+
assert status["completed"] == 1
172+
assert status["failed"] == 0
173+
assert completeness["scope_exclusions"][0]["path"] == "assets/font.ttf"
174+
assert completeness["status"] == "complete"
175+
assert completeness["coverage_percent"] == 100.0
176+
177+
128178
def test_missing_terminal_row_becomes_fatal_unaccounted_work() -> None:
129179
work_id = inspection_work_id("behavioral_ast", "broken.py", None, None)
130180

‎tests/test_inspection_ledger.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from skillspector.inspection_ledger import (
99
LedgerOutcome,
1010
LedgerReason,
11+
LedgerRecordType,
1112
analyzer_status_for_events,
1213
inspection_work_id,
1314
ledger_event,
@@ -69,6 +70,28 @@ def test_analyzer_status_for_events_summarizes_terminal_work() -> None:
6970
}
7071

7172

73+
def test_analyzer_status_for_scope_boundaries_has_no_planned_work() -> None:
74+
"""Scope-boundary records are not work assigned to the analyzer."""
75+
event = ledger_event(
76+
outcome=LedgerOutcome.OUT_OF_SCOPE,
77+
record_type=LedgerRecordType.SCOPE_BOUNDARY,
78+
phase="static",
79+
analyzer_id="static_test",
80+
path="assets/font.ttf",
81+
reason=LedgerReason.BINARY_CONTENT,
82+
)
83+
84+
status = analyzer_status_for_events("static_test", [event])
85+
86+
assert status == {
87+
"analyzer_id": "static_test",
88+
"status": "not_applicable",
89+
"planned_work": [],
90+
"reason_code": LedgerReason.NO_APPLICABLE_FILES,
91+
"message": "No files matched this analyzer's applicability contract.",
92+
}
93+
94+
7295
@pytest.mark.parametrize(
7396
("reason", "expected_outcome"),
7497
[

0 commit comments

Comments
 (0)