vigil-codeintel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vigil_codeintel-0.1.0.dist-info/METADATA +780 -0
- vigil_codeintel-0.1.0.dist-info/RECORD +131 -0
- vigil_codeintel-0.1.0.dist-info/WHEEL +5 -0
- vigil_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
- vigil_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
- vigil_codeintel-0.1.0.dist-info/top_level.txt +3 -0
- vigil_forensic/__init__.py +224 -0
- vigil_forensic/_git_utils.py +178 -0
- vigil_forensic/_shared.py +510 -0
- vigil_forensic/_stubs.py +156 -0
- vigil_forensic/gate_checks/__init__.py +1 -0
- vigil_forensic/gate_checks/_ast_helpers.py +629 -0
- vigil_forensic/gate_checks/_deployment_detector.py +573 -0
- vigil_forensic/gate_checks/atomic_write_checks.py +1143 -0
- vigil_forensic/gate_checks/authority_checks.py +95 -0
- vigil_forensic/gate_checks/boundary_breach_checks.py +202 -0
- vigil_forensic/gate_checks/broad_except_checks.py +301 -0
- vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py +365 -0
- vigil_forensic/gate_checks/common.py +253 -0
- vigil_forensic/gate_checks/config_safety_checks.py +704 -0
- vigil_forensic/gate_checks/config_ssot_checks.py +78 -0
- vigil_forensic/gate_checks/conflict_checks.py +193 -0
- vigil_forensic/gate_checks/context_fallback_checks.py +697 -0
- vigil_forensic/gate_checks/context_health_checks.py +289 -0
- vigil_forensic/gate_checks/contract_shape_drift_checks.py +459 -0
- vigil_forensic/gate_checks/dirty_baseline_check.py +274 -0
- vigil_forensic/gate_checks/duplication_checks.py +387 -0
- vigil_forensic/gate_checks/embedded_string_checks.py +123 -0
- vigil_forensic/gate_checks/empty_output_checks.py +87 -0
- vigil_forensic/gate_checks/encoding_checks.py +847 -0
- vigil_forensic/gate_checks/export_completeness_checks.py +156 -0
- vigil_forensic/gate_checks/fallback_checks.py +41 -0
- vigil_forensic/gate_checks/file_proliferation_checks.py +171 -0
- vigil_forensic/gate_checks/fix_without_test_checks.py +69 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py +9 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py +71 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py +322 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/core.py +273 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py +203 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py +666 -0
- vigil_forensic/gate_checks/forensic_clusters/__init__.py +193 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist.py +426 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py +302 -0
- vigil_forensic/gate_checks/forensic_clusters/api_protocol.py +231 -0
- vigil_forensic/gate_checks/forensic_clusters/async_quality.py +1156 -0
- vigil_forensic/gate_checks/forensic_clusters/code_style.py +808 -0
- vigil_forensic/gate_checks/forensic_clusters/core.py +319 -0
- vigil_forensic/gate_checks/forensic_clusters/data_quality.py +763 -0
- vigil_forensic/gate_checks/forensic_clusters/dead_code.py +480 -0
- vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py +842 -0
- vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py +240 -0
- vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py +556 -0
- vigil_forensic/gate_checks/forensic_clusters/static_analysis.py +834 -0
- vigil_forensic/gate_checks/forensic_clusters/structural_quality.py +298 -0
- vigil_forensic/gate_checks/god_object_zones_checks.py +173 -0
- vigil_forensic/gate_checks/hallucination_checks.py +566 -0
- vigil_forensic/gate_checks/hunter_artifact_completeness_check.py +139 -0
- vigil_forensic/gate_checks/implementation_overfit_checks.py +380 -0
- vigil_forensic/gate_checks/import_integrity_checks.py +233 -0
- vigil_forensic/gate_checks/imports_in_function_checks.py +283 -0
- vigil_forensic/gate_checks/ml_checks.py +318 -0
- vigil_forensic/gate_checks/performance_checks.py +106 -0
- vigil_forensic/gate_checks/project_specific_runner.py +691 -0
- vigil_forensic/gate_checks/provider_capability_checks.py +73 -0
- vigil_forensic/gate_checks/refactor_completeness_checks.py +274 -0
- vigil_forensic/gate_checks/reliability_checks.py +389 -0
- vigil_forensic/gate_checks/reporting_checks.py +55 -0
- vigil_forensic/gate_checks/runtime_behavior_checks.py +220 -0
- vigil_forensic/gate_checks/security_injection_checks.py +332 -0
- vigil_forensic/gate_checks/semantic_intent_checks.py +139 -0
- vigil_forensic/gate_checks/size_complexity_checks.py +336 -0
- vigil_forensic/gate_checks/stuck_feature_flag_checks.py +354 -0
- vigil_forensic/gate_checks/syntax_validity_checks.py +217 -0
- vigil_forensic/gate_checks/temporal_freshness_checks.py +79 -0
- vigil_forensic/gate_checks/test_quality_checks.py +946 -0
- vigil_forensic/gate_checks/testing_checks.py +149 -0
- vigil_forensic/gate_checks/toctou_checks.py +367 -0
- vigil_forensic/gate_checks/type_checking_checks.py +316 -0
- vigil_forensic/gate_models.py +392 -0
- vigil_forensic/gate_packs/__init__.py +1 -0
- vigil_forensic/gate_packs/universal.py +179 -0
- vigil_forensic/gate_profile.json +31 -0
- vigil_forensic/gate_registry.py +21 -0
- vigil_forensic/language_profiles.py +219 -0
- vigil_forensic/meta_findings.py +207 -0
- vigil_forensic/self_audit.py +725 -0
- vigil_forensic/source_analysis.py +175 -0
- vigil_mapper/__init__.py +103 -0
- vigil_mapper/_ast_helpers_minimal.py +229 -0
- vigil_mapper/_extract_imports_impl.py +123 -0
- vigil_mapper/_file_count_guard.py +129 -0
- vigil_mapper/_git_utils.py +178 -0
- vigil_mapper/_runtime_ast.py +438 -0
- vigil_mapper/_runtime_dispatch.py +137 -0
- vigil_mapper/_seed_helpers.py +82 -0
- vigil_mapper/authority_builder.py +1102 -0
- vigil_mapper/cli_entry.py +731 -0
- vigil_mapper/conflict_builder.py +818 -0
- vigil_mapper/data_contract_builder.py +446 -0
- vigil_mapper/findings_builder.py +716 -0
- vigil_mapper/fingerprint.py +53 -0
- vigil_mapper/hotspot_builder.py +539 -0
- vigil_mapper/map_common.py +449 -0
- vigil_mapper/map_errors.py +55 -0
- vigil_mapper/map_models.py +431 -0
- vigil_mapper/map_models_ext.py +206 -0
- vigil_mapper/map_models_findings.py +130 -0
- vigil_mapper/map_storage.py +455 -0
- vigil_mapper/parse_cache.py +795 -0
- vigil_mapper/refactor_boundary_builder.py +266 -0
- vigil_mapper/runtime_builder.py +527 -0
- vigil_mapper/runtime_tracer.py +243 -0
- vigil_mapper/runtime_tracer_entry.py +199 -0
- vigil_mapper/semantic_diff.py +71 -0
- vigil_mapper/source_adapters/__init__.py +109 -0
- vigil_mapper/source_adapters/_base.py +264 -0
- vigil_mapper/source_adapters/_ir.py +156 -0
- vigil_mapper/source_adapters/_lexer.py +309 -0
- vigil_mapper/source_adapters/_patterns.py +212 -0
- vigil_mapper/source_adapters/_treesitter.py +182 -0
- vigil_mapper/source_adapters/go.py +553 -0
- vigil_mapper/source_adapters/java.py +541 -0
- vigil_mapper/source_adapters/javascript.py +626 -0
- vigil_mapper/source_adapters/python.py +325 -0
- vigil_mapper/source_adapters/typescript.py +749 -0
- vigil_mapper/structural_builder.py +586 -0
- vigil_mcp/__init__.py +1 -0
- vigil_mcp/_jobs.py +587 -0
- vigil_mcp/_paths.py +93 -0
- vigil_mcp/forensic_server.py +419 -0
- vigil_mcp/map_server.py +452 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
|
|
5
|
+
from vigil_forensic._shared import EvidenceReference, GateCategory, GateImpact, GateSeverity, RepairKind
|
|
6
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
7
|
+
from ..source_analysis import get_language_id, is_source_file
|
|
8
|
+
from .common import build_check_result, build_finding, iter_touched_snapshots
|
|
9
|
+
import logging
|
|
10
|
+
_log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _find_brace_imbalance(text: str) -> tuple[int, str] | None:
|
|
14
|
+
"""Return ``(line_number, reason)`` for a brace/bracket/paren imbalance.
|
|
15
|
+
|
|
16
|
+
Lightweight textual check for non-Python sources. Tracks ``() [] {}`` but
|
|
17
|
+
respects string literals (`'` `"` backtick) and ``//`` / ``#`` /
|
|
18
|
+
``/* ... */`` comments. This is intentionally conservative: when in
|
|
19
|
+
doubt it returns ``None`` (no finding) rather than a speculative FP.
|
|
20
|
+
"""
|
|
21
|
+
depth_paren = 0
|
|
22
|
+
depth_bracket = 0
|
|
23
|
+
depth_brace = 0
|
|
24
|
+
|
|
25
|
+
in_line_comment = False
|
|
26
|
+
in_block_comment = False
|
|
27
|
+
in_string: str | None = None
|
|
28
|
+
string_start_line = 0
|
|
29
|
+
lineno = 1
|
|
30
|
+
last_open: list[tuple[str, int]] = []
|
|
31
|
+
|
|
32
|
+
i = 0
|
|
33
|
+
n = len(text)
|
|
34
|
+
while i < n:
|
|
35
|
+
ch = text[i]
|
|
36
|
+
nxt = text[i + 1] if i + 1 < n else ""
|
|
37
|
+
|
|
38
|
+
if ch == "\n":
|
|
39
|
+
lineno += 1
|
|
40
|
+
in_line_comment = False
|
|
41
|
+
i += 1
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if in_line_comment:
|
|
45
|
+
i += 1
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
if in_block_comment:
|
|
49
|
+
if ch == "*" and nxt == "/":
|
|
50
|
+
in_block_comment = False
|
|
51
|
+
i += 2
|
|
52
|
+
continue
|
|
53
|
+
i += 1
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if in_string is not None:
|
|
57
|
+
if ch == "\\":
|
|
58
|
+
i += 2 # skip escaped char
|
|
59
|
+
continue
|
|
60
|
+
if ch == in_string:
|
|
61
|
+
in_string = None
|
|
62
|
+
i += 1
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Not in string/comment.
|
|
66
|
+
if ch == "/" and nxt == "/":
|
|
67
|
+
in_line_comment = True
|
|
68
|
+
i += 2
|
|
69
|
+
continue
|
|
70
|
+
if ch == "/" and nxt == "*":
|
|
71
|
+
in_block_comment = True
|
|
72
|
+
i += 2
|
|
73
|
+
continue
|
|
74
|
+
if ch == "#":
|
|
75
|
+
# Only treat '#' as a line comment for shell-ish / generic sources;
|
|
76
|
+
# harmless for JS because it's never a valid operator start.
|
|
77
|
+
in_line_comment = True
|
|
78
|
+
i += 1
|
|
79
|
+
continue
|
|
80
|
+
if ch in ("'", '"', "`"):
|
|
81
|
+
in_string = ch
|
|
82
|
+
string_start_line = lineno
|
|
83
|
+
i += 1
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
if ch == "(":
|
|
87
|
+
depth_paren += 1
|
|
88
|
+
last_open.append(("(", lineno))
|
|
89
|
+
elif ch == ")":
|
|
90
|
+
depth_paren -= 1
|
|
91
|
+
if depth_paren < 0:
|
|
92
|
+
return lineno, "unmatched ')'"
|
|
93
|
+
if last_open and last_open[-1][0] == "(":
|
|
94
|
+
last_open.pop()
|
|
95
|
+
elif ch == "[":
|
|
96
|
+
depth_bracket += 1
|
|
97
|
+
last_open.append(("[", lineno))
|
|
98
|
+
elif ch == "]":
|
|
99
|
+
depth_bracket -= 1
|
|
100
|
+
if depth_bracket < 0:
|
|
101
|
+
return lineno, "unmatched ']'"
|
|
102
|
+
if last_open and last_open[-1][0] == "[":
|
|
103
|
+
last_open.pop()
|
|
104
|
+
elif ch == "{":
|
|
105
|
+
depth_brace += 1
|
|
106
|
+
last_open.append(("{", lineno))
|
|
107
|
+
elif ch == "}":
|
|
108
|
+
depth_brace -= 1
|
|
109
|
+
if depth_brace < 0:
|
|
110
|
+
return lineno, "unmatched '}'"
|
|
111
|
+
if last_open and last_open[-1][0] == "{":
|
|
112
|
+
last_open.pop()
|
|
113
|
+
|
|
114
|
+
i += 1
|
|
115
|
+
|
|
116
|
+
if in_string is not None:
|
|
117
|
+
return string_start_line, f"unclosed string literal (opened line {string_start_line})"
|
|
118
|
+
if in_block_comment:
|
|
119
|
+
return lineno, "unclosed block comment"
|
|
120
|
+
if depth_paren or depth_bracket or depth_brace:
|
|
121
|
+
if last_open:
|
|
122
|
+
br, ln = last_open[-1]
|
|
123
|
+
return ln, f"unclosed '{br}' (opened line {ln})"
|
|
124
|
+
return lineno, "bracket depth nonzero at EOF"
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Languages for which we apply the Python ``ast.parse`` check. Every other
|
|
129
|
+
# source language uses the textual balanced-brace fallback below -- we never
|
|
130
|
+
# try to parse non-Python as Python (F12c fix: same category of bug as the
|
|
131
|
+
# F9a Python-only branch).
|
|
132
|
+
_PYTHON_LANGUAGE_IDS: frozenset[str] = frozenset({"python"})
|
|
133
|
+
|
|
134
|
+
# Languages for which a textual brace/bracket/paren balance check is a useful
|
|
135
|
+
# heuristic. Shell/PowerShell/Go-style sources are NOT listed because their
|
|
136
|
+
# syntax differs enough that brace balance isn't a reliable signal.
|
|
137
|
+
_BRACE_LANGUAGE_IDS: frozenset[str] = frozenset({
|
|
138
|
+
"javascript", "typescript",
|
|
139
|
+
"java", "kotlin", "scala",
|
|
140
|
+
"cpp", "c", "csharp",
|
|
141
|
+
"rust",
|
|
142
|
+
"json",
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def run_syntax_validity_checks(ctx: PostExecGateContext):
|
|
147
|
+
"""Check that touched source files parse / balance correctly.
|
|
148
|
+
|
|
149
|
+
Language dispatch:
|
|
150
|
+
Python -> ``ast.parse()``
|
|
151
|
+
JS / TS / Java / C / ... -> textual brace/paren balance only
|
|
152
|
+
Shell / PS1 / Go / ... -> skipped (no reliable lightweight check)
|
|
153
|
+
|
|
154
|
+
Never attempts to parse a non-Python file as Python -- that was the F12c
|
|
155
|
+
bug (100% FP on ``.ts``/``.tsx``/``.jsx``/``.go`` snapshots).
|
|
156
|
+
"""
|
|
157
|
+
findings = []
|
|
158
|
+
for snapshot in iter_touched_snapshots(ctx):
|
|
159
|
+
if not snapshot.exists or not is_source_file(snapshot.path):
|
|
160
|
+
continue
|
|
161
|
+
if not snapshot.text.strip():
|
|
162
|
+
continue # empty files are handled by empty_output_checks
|
|
163
|
+
|
|
164
|
+
lang = get_language_id(snapshot.path)
|
|
165
|
+
|
|
166
|
+
if lang in _PYTHON_LANGUAGE_IDS:
|
|
167
|
+
try:
|
|
168
|
+
ast.parse(snapshot.text, filename=snapshot.path)
|
|
169
|
+
except SyntaxError as exc:
|
|
170
|
+
line_info = f"line {exc.lineno}" if exc.lineno else "unknown line"
|
|
171
|
+
findings.append(
|
|
172
|
+
build_finding(
|
|
173
|
+
check_id="syntax_validity.parse_error",
|
|
174
|
+
category=GateCategory.REPORTING,
|
|
175
|
+
title=f"SyntaxError in {snapshot.path} ({line_info})",
|
|
176
|
+
severity=GateSeverity.HIGH,
|
|
177
|
+
impact=GateImpact.REVISE,
|
|
178
|
+
summary=(
|
|
179
|
+
f"File {snapshot.path} contains invalid Python syntax at {line_info}: "
|
|
180
|
+
f"{str(exc.msg)[:200]}. This will cause ImportError at runtime."
|
|
181
|
+
),
|
|
182
|
+
recommendation="Fix the syntax error before accepting the executor result.",
|
|
183
|
+
evidence=[EvidenceReference(kind="file", path=snapshot.path, detail=f"SyntaxError:{line_info}")],
|
|
184
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
185
|
+
executor_action="Fix syntax error",
|
|
186
|
+
proof_required="Valid syntax",
|
|
187
|
+
allowlist_allowed=False,
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
elif lang in _BRACE_LANGUAGE_IDS:
|
|
191
|
+
imbalance = _find_brace_imbalance(snapshot.text)
|
|
192
|
+
if imbalance is not None:
|
|
193
|
+
line_no, reason = imbalance
|
|
194
|
+
findings.append(
|
|
195
|
+
build_finding(
|
|
196
|
+
check_id="syntax_validity.parse_error",
|
|
197
|
+
category=GateCategory.REPORTING,
|
|
198
|
+
title=f"Brace/bracket imbalance in {snapshot.path} (line {line_no})",
|
|
199
|
+
severity=GateSeverity.HIGH,
|
|
200
|
+
impact=GateImpact.REVISE,
|
|
201
|
+
summary=(
|
|
202
|
+
f"File {snapshot.path} has {reason} at line {line_no}. "
|
|
203
|
+
"Lightweight textual check -- fix the imbalance."
|
|
204
|
+
),
|
|
205
|
+
recommendation="Close the unmatched bracket or remove the stray one.",
|
|
206
|
+
evidence=[EvidenceReference(kind="file", path=snapshot.path, detail=f"imbalance:{reason}")],
|
|
207
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
208
|
+
executor_action="Fix bracket imbalance",
|
|
209
|
+
proof_required="Balanced brackets",
|
|
210
|
+
allowlist_allowed=False,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
# else: language without a lightweight check (shell, powershell, go, ...)
|
|
214
|
+
# -- emit 0 findings rather than 100% FP. Real syntax errors surface
|
|
215
|
+
# via the language's own tooling.
|
|
216
|
+
|
|
217
|
+
return build_check_result(check_id="syntax_validity", category=GateCategory.REPORTING, findings=findings)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from vigil_forensic._shared import EvidenceReference, GateCategory, GateImpact, GateSeverity
|
|
6
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
7
|
+
from .common import build_check_result, build_finding
|
|
8
|
+
import logging
|
|
9
|
+
_log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_temporal_freshness_checks(ctx: PostExecGateContext):
|
|
13
|
+
findings = []
|
|
14
|
+
handoff_path = ctx.artifact_refs.get("executor_handoff", "")
|
|
15
|
+
prompt_path = ctx.session_artifacts.get("system_prompt", "")
|
|
16
|
+
forensic_path = ctx.artifact_refs.get("forensic", "")
|
|
17
|
+
|
|
18
|
+
handoff_ts = _mtime(handoff_path)
|
|
19
|
+
prompt_ts = _mtime(prompt_path)
|
|
20
|
+
forensic_ts = _mtime(forensic_path)
|
|
21
|
+
|
|
22
|
+
if prompt_ts > 0.0 and handoff_ts > 0.0 and handoff_ts + 0.001 < prompt_ts:
|
|
23
|
+
findings.append(
|
|
24
|
+
build_finding(
|
|
25
|
+
check_id="temporal.handoff_before_prompt",
|
|
26
|
+
category=GateCategory.TEMPORAL_FRESHNESS,
|
|
27
|
+
title="Executor handoff is older than the Claude prompt",
|
|
28
|
+
severity=GateSeverity.CRITICAL,
|
|
29
|
+
impact=GateImpact.BLOCK,
|
|
30
|
+
summary="The handoff artifact timestamp predates the prompt artifact for the same session.",
|
|
31
|
+
recommendation="Verify artifact selection by current session/attempt; do not reuse stale handoff files.",
|
|
32
|
+
evidence=[
|
|
33
|
+
EvidenceReference(kind="artifact", path=prompt_path, detail=f"prompt_mtime={prompt_ts}"),
|
|
34
|
+
EvidenceReference(kind="artifact", path=handoff_path, detail=f"handoff_mtime={handoff_ts}"),
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
repair_kind='refactor',
|
|
38
|
+
executor_action='Address finding details',
|
|
39
|
+
proof_required='Freshness acceptable',
|
|
40
|
+
allowlist_allowed=False,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if handoff_ts > 0.0 and forensic_ts > 0.0 and forensic_ts + 0.001 < handoff_ts:
|
|
45
|
+
findings.append(
|
|
46
|
+
build_finding(
|
|
47
|
+
check_id="temporal.forensic_before_handoff",
|
|
48
|
+
category=GateCategory.TEMPORAL_FRESHNESS,
|
|
49
|
+
title="Forensic evidence is older than the executor handoff",
|
|
50
|
+
severity=GateSeverity.HIGH,
|
|
51
|
+
impact=GateImpact.REVISE,
|
|
52
|
+
summary="The forensic artifact appears older than the handoff it is supposed to validate.",
|
|
53
|
+
recommendation="Rerun forensic validation after the executor handoff and persist fresh evidence.",
|
|
54
|
+
evidence=[
|
|
55
|
+
EvidenceReference(kind="artifact", path=handoff_path, detail=f"handoff_mtime={handoff_ts}"),
|
|
56
|
+
EvidenceReference(kind="artifact", path=forensic_path, detail=f"forensic_mtime={forensic_ts}"),
|
|
57
|
+
],
|
|
58
|
+
|
|
59
|
+
repair_kind='refactor',
|
|
60
|
+
executor_action='Address finding details',
|
|
61
|
+
proof_required='Freshness acceptable',
|
|
62
|
+
allowlist_allowed=False,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return build_check_result(
|
|
67
|
+
check_id="temporal_freshness",
|
|
68
|
+
category=GateCategory.TEMPORAL_FRESHNESS,
|
|
69
|
+
findings=findings,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _mtime(path_text: str) -> float:
|
|
74
|
+
if not path_text:
|
|
75
|
+
return 0.0
|
|
76
|
+
path = Path(path_text)
|
|
77
|
+
if not path.exists() or not path.is_file():
|
|
78
|
+
return 0.0
|
|
79
|
+
return path.stat().st_mtime
|