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,319 @@
|
|
|
1
|
+
"""Core types, adapter, language detection, and integrity clusters 1-9.
|
|
2
|
+
|
|
3
|
+
Clusters:
|
|
4
|
+
1 - Declared Capability != Actual Capability
|
|
5
|
+
2 - Success Without Proof
|
|
6
|
+
3 - Non-Authoritative Proxy Mistaken as Truth
|
|
7
|
+
4 - Config Accepted But Ignored
|
|
8
|
+
5 - Rendered Contract != Live Contract
|
|
9
|
+
6 - State Divergence
|
|
10
|
+
7 - Fallback Hides Truth
|
|
11
|
+
8 - Dead Surface Drift
|
|
12
|
+
9 - Phantom Capability (LLM-specific)
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Callable, Optional, Sequence
|
|
18
|
+
|
|
19
|
+
from ...gate_models import (
|
|
20
|
+
EvidenceReference,
|
|
21
|
+
GateCategory,
|
|
22
|
+
GateFinding,
|
|
23
|
+
GateImpact,
|
|
24
|
+
GateSeverity,
|
|
25
|
+
RepairKind,
|
|
26
|
+
)
|
|
27
|
+
from ..common import build_finding
|
|
28
|
+
import logging
|
|
29
|
+
_log = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Language detection
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def detect_language(file_path: str) -> str:
|
|
38
|
+
"""Detect programming language from file extension."""
|
|
39
|
+
ext = file_path.rsplit(".", 1)[-1].lower() if "." in file_path else ""
|
|
40
|
+
return {
|
|
41
|
+
"py": "python", "pyw": "python",
|
|
42
|
+
"js": "javascript", "mjs": "javascript", "cjs": "javascript",
|
|
43
|
+
"ts": "typescript", "tsx": "typescript", "jsx": "javascript",
|
|
44
|
+
"rb": "ruby", "go": "go", "rs": "rust",
|
|
45
|
+
"java": "java", "kt": "kotlin", "scala": "scala",
|
|
46
|
+
"cs": "csharp", "cpp": "cpp", "c": "c", "h": "c",
|
|
47
|
+
"swift": "swift", "php": "php", "lua": "lua",
|
|
48
|
+
"sh": "shell", "bash": "shell", "zsh": "shell",
|
|
49
|
+
"html": "html", "css": "css", "scss": "scss",
|
|
50
|
+
"json": "json", "yaml": "yaml", "yml": "yaml", "toml": "toml",
|
|
51
|
+
"md": "markdown", "rst": "restructuredtext",
|
|
52
|
+
"sql": "sql",
|
|
53
|
+
}.get(ext, "unknown")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Helper: build an insufficient-evidence finding
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _insufficient_evidence_finding(
|
|
62
|
+
check_id: str,
|
|
63
|
+
category: GateCategory,
|
|
64
|
+
cluster: str,
|
|
65
|
+
explanation: str,
|
|
66
|
+
) -> GateFinding:
|
|
67
|
+
return build_finding(
|
|
68
|
+
check_id=check_id,
|
|
69
|
+
category=category,
|
|
70
|
+
title=f"[{cluster}] insufficient evidence",
|
|
71
|
+
severity=GateSeverity.INFO,
|
|
72
|
+
impact=GateImpact.WARN,
|
|
73
|
+
summary=explanation,
|
|
74
|
+
recommendation="Gather more evidence before re-running this check.",
|
|
75
|
+
repair_kind=RepairKind.ADD_PROOF.value,
|
|
76
|
+
executor_action="Gather more evidence before re-running",
|
|
77
|
+
proof_required="",
|
|
78
|
+
allowlist_allowed=True,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Cluster 2: Success Without Proof
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class ProofRequirement:
|
|
89
|
+
"""A field/artifact that must be present for success to be truthful."""
|
|
90
|
+
name: str
|
|
91
|
+
field_path: str
|
|
92
|
+
required: bool = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def assess_success_proof(
|
|
96
|
+
status: dict[str, object],
|
|
97
|
+
proof_requirements: Sequence[ProofRequirement],
|
|
98
|
+
success_field: str = "ok",
|
|
99
|
+
completion_field: str = "phase",
|
|
100
|
+
completion_value: str = "completed",
|
|
101
|
+
) -> list[GateFinding]:
|
|
102
|
+
"""Cluster 2: Verify success claims have required proof."""
|
|
103
|
+
phase = str(status.get(completion_field) or "")
|
|
104
|
+
if phase != completion_value:
|
|
105
|
+
return [] # not a success claim
|
|
106
|
+
|
|
107
|
+
ok = status.get(success_field)
|
|
108
|
+
if ok is False:
|
|
109
|
+
return [] # explicit failure, not a false success claim
|
|
110
|
+
|
|
111
|
+
findings: list[GateFinding] = []
|
|
112
|
+
for req in proof_requirements:
|
|
113
|
+
if not req.required:
|
|
114
|
+
continue
|
|
115
|
+
value = status.get(req.field_path)
|
|
116
|
+
has_value = bool(value) if isinstance(value, str) else value is not None
|
|
117
|
+
if not has_value:
|
|
118
|
+
findings.append(build_finding(
|
|
119
|
+
check_id="success_proof",
|
|
120
|
+
category=GateCategory.REPORTING,
|
|
121
|
+
title=f"[success_without_proof] missing: {req.name}",
|
|
122
|
+
severity=GateSeverity.MEDIUM,
|
|
123
|
+
impact=GateImpact.REVISE,
|
|
124
|
+
summary=f"Success claimed but missing proof: {req.field_path}",
|
|
125
|
+
recommendation="Provide required proof artifacts before claiming success.",
|
|
126
|
+
evidence=(EvidenceReference(kind="probe", detail=f"MISSING: {req.name}", ok=False),),
|
|
127
|
+
repair_kind=RepairKind.ADD_MISSING_PROOF.value,
|
|
128
|
+
executor_action=f"Add missing proof: {req.name}",
|
|
129
|
+
))
|
|
130
|
+
return findings
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# Cluster 3: Non-Authoritative Proxy Mistaken as Truth
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def assess_source_truthfulness(
|
|
139
|
+
stated_source: str,
|
|
140
|
+
actual_source: str,
|
|
141
|
+
label_shown: str = "",
|
|
142
|
+
) -> list[GateFinding]:
|
|
143
|
+
"""Cluster 3: Verify source labeling is honest.
|
|
144
|
+
|
|
145
|
+
stated_source/actual_source: "authoritative"|"proxy"|"cache"|"preview"|"stale"|"unknown"
|
|
146
|
+
"""
|
|
147
|
+
valid_sources = {"authoritative", "proxy", "cache", "preview", "stale", "unknown"}
|
|
148
|
+
if actual_source not in valid_sources:
|
|
149
|
+
return [_insufficient_evidence_finding(
|
|
150
|
+
check_id="source_truthfulness",
|
|
151
|
+
category=GateCategory.TRUTH_BOUNDARY,
|
|
152
|
+
cluster="proxy_as_truth",
|
|
153
|
+
explanation=f"Unknown actual source type: {actual_source}",
|
|
154
|
+
)]
|
|
155
|
+
|
|
156
|
+
if actual_source == "unknown":
|
|
157
|
+
return [_insufficient_evidence_finding(
|
|
158
|
+
check_id="source_truthfulness",
|
|
159
|
+
category=GateCategory.TRUTH_BOUNDARY,
|
|
160
|
+
cluster="proxy_as_truth",
|
|
161
|
+
explanation="Actual source is unknown -- cannot assess truthfulness",
|
|
162
|
+
)]
|
|
163
|
+
|
|
164
|
+
if actual_source != "authoritative" and stated_source == "authoritative":
|
|
165
|
+
return [build_finding(
|
|
166
|
+
check_id="source_truthfulness",
|
|
167
|
+
category=GateCategory.TRUTH_BOUNDARY,
|
|
168
|
+
title=f"[proxy_as_truth] {label_shown or 'source_label'}",
|
|
169
|
+
severity=GateSeverity.MEDIUM,
|
|
170
|
+
impact=GateImpact.REVISE,
|
|
171
|
+
summary=f"Stated '{stated_source}' but actual is '{actual_source}'",
|
|
172
|
+
recommendation=f"Non-authoritative source ({actual_source}) presented as authoritative",
|
|
173
|
+
evidence=(EvidenceReference(kind="probe", detail=f"Stated '{stated_source}' but actual is '{actual_source}'", ok=False),),
|
|
174
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
175
|
+
executor_action="Correct source labeling to reflect actual data provenance",
|
|
176
|
+
)]
|
|
177
|
+
|
|
178
|
+
return []
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Cluster 4: Config Accepted But Ignored
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def assess_config_applied(
|
|
187
|
+
config_key: str,
|
|
188
|
+
config_value: object,
|
|
189
|
+
persisted: bool,
|
|
190
|
+
consumed_by_runtime: bool,
|
|
191
|
+
) -> list[GateFinding]:
|
|
192
|
+
"""Cluster 4: Verify config is not just accepted but applied."""
|
|
193
|
+
if config_key == "" or config_value is None:
|
|
194
|
+
return [] # not applicable
|
|
195
|
+
|
|
196
|
+
findings: list[GateFinding] = []
|
|
197
|
+
if not persisted:
|
|
198
|
+
findings.append(build_finding(
|
|
199
|
+
check_id="config_applied",
|
|
200
|
+
category=GateCategory.CONFIG_SSOT,
|
|
201
|
+
title=f"[config_accepted_ignored] {config_key}.persisted",
|
|
202
|
+
severity=GateSeverity.MEDIUM,
|
|
203
|
+
impact=GateImpact.REVISE,
|
|
204
|
+
summary=f"Config '{config_key}' accepted but not persisted",
|
|
205
|
+
recommendation="Persist config value before returning success.",
|
|
206
|
+
evidence=(EvidenceReference(kind="probe", detail="Config NOT persisted", ok=False),),
|
|
207
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
208
|
+
executor_action=f"Persist config '{config_key}'",
|
|
209
|
+
))
|
|
210
|
+
elif not consumed_by_runtime:
|
|
211
|
+
findings.append(build_finding(
|
|
212
|
+
check_id="config_applied",
|
|
213
|
+
category=GateCategory.CONFIG_SSOT,
|
|
214
|
+
title=f"[config_accepted_ignored] {config_key}.consumed",
|
|
215
|
+
severity=GateSeverity.MEDIUM,
|
|
216
|
+
impact=GateImpact.REVISE,
|
|
217
|
+
summary=f"Config '{config_key}' accepted and persisted but not consumed by runtime",
|
|
218
|
+
recommendation="Ensure runtime actually reads and applies the persisted config.",
|
|
219
|
+
evidence=(EvidenceReference(kind="probe", detail="Config NOT consumed by runtime", ok=False),),
|
|
220
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
221
|
+
executor_action=f"Wire config '{config_key}' into runtime consumption path",
|
|
222
|
+
))
|
|
223
|
+
return findings
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
# Cluster 6: State Divergence
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def assess_state_consistency(
|
|
232
|
+
representations: dict[str, object],
|
|
233
|
+
expected_equal_keys: Sequence[tuple[str, str]],
|
|
234
|
+
normalizer: Optional[Callable[[object], object]] = None,
|
|
235
|
+
) -> list[GateFinding]:
|
|
236
|
+
"""Cluster 6: Multiple representations of the same state must agree.
|
|
237
|
+
|
|
238
|
+
representations: {"model": value, "persisted": value, "displayed": value}
|
|
239
|
+
expected_equal_keys: [("model", "persisted"), ("model", "displayed")]
|
|
240
|
+
"""
|
|
241
|
+
if len(representations) < 2:
|
|
242
|
+
return [] # not applicable
|
|
243
|
+
|
|
244
|
+
norm = normalizer or (lambda x: x)
|
|
245
|
+
findings: list[GateFinding] = []
|
|
246
|
+
|
|
247
|
+
for key_a, key_b in expected_equal_keys:
|
|
248
|
+
val_a = representations.get(key_a)
|
|
249
|
+
val_b = representations.get(key_b)
|
|
250
|
+
if val_a is None or val_b is None:
|
|
251
|
+
missing = key_a if val_a is None else key_b
|
|
252
|
+
findings.append(build_finding(
|
|
253
|
+
check_id="state_consistency",
|
|
254
|
+
category=GateCategory.DRIFT,
|
|
255
|
+
title=f"[state_divergence] {key_a}=={key_b}",
|
|
256
|
+
severity=GateSeverity.MEDIUM,
|
|
257
|
+
impact=GateImpact.REVISE,
|
|
258
|
+
summary=f"Missing representation: {missing}",
|
|
259
|
+
recommendation=f"State divergence: {key_a}=={key_b}. Ensure all representations are consistent.",
|
|
260
|
+
evidence=(EvidenceReference(kind="probe", detail=f"Missing representation: {missing}", ok=False),),
|
|
261
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
262
|
+
executor_action=f"Provide missing representation: {missing}",
|
|
263
|
+
))
|
|
264
|
+
continue
|
|
265
|
+
if norm(val_a) != norm(val_b):
|
|
266
|
+
findings.append(build_finding(
|
|
267
|
+
check_id="state_consistency",
|
|
268
|
+
category=GateCategory.DRIFT,
|
|
269
|
+
title=f"[state_divergence] {key_a}=={key_b}",
|
|
270
|
+
severity=GateSeverity.MEDIUM,
|
|
271
|
+
impact=GateImpact.REVISE,
|
|
272
|
+
summary=f"State divergence: {key_a} vs {key_b}",
|
|
273
|
+
recommendation=f"State divergence detected: {key_a}=={key_b}. Synchronize representations.",
|
|
274
|
+
evidence=(EvidenceReference(kind="probe", detail=f"DIVERGENT: {key_a} vs {key_b}", ok=False),),
|
|
275
|
+
repair_kind=RepairKind.FIX_CONTRACT.value,
|
|
276
|
+
executor_action=f"Synchronize {key_a} and {key_b}",
|
|
277
|
+
))
|
|
278
|
+
return findings
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
# Cluster 7: Fallback Hides Truth
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def assess_fallback_transparency(
|
|
287
|
+
primary_available: bool,
|
|
288
|
+
fallback_used: bool,
|
|
289
|
+
degradation_labeled: bool,
|
|
290
|
+
) -> list[GateFinding]:
|
|
291
|
+
"""Cluster 7: Hidden fallback = fail; labeled degradation = pass."""
|
|
292
|
+
if primary_available and not fallback_used:
|
|
293
|
+
return [] # PASS
|
|
294
|
+
|
|
295
|
+
if not primary_available and not fallback_used:
|
|
296
|
+
return [_insufficient_evidence_finding(
|
|
297
|
+
check_id="fallback_transparency",
|
|
298
|
+
category=GateCategory.FALLBACK,
|
|
299
|
+
cluster="fallback_hides_truth",
|
|
300
|
+
explanation="Primary unavailable, no fallback used -- state unclear",
|
|
301
|
+
)]
|
|
302
|
+
|
|
303
|
+
if fallback_used and not degradation_labeled:
|
|
304
|
+
return [build_finding(
|
|
305
|
+
check_id="fallback_transparency",
|
|
306
|
+
category=GateCategory.FALLBACK,
|
|
307
|
+
title=f"[fallback_hides_truth] fallback_label",
|
|
308
|
+
severity=GateSeverity.MEDIUM,
|
|
309
|
+
impact=GateImpact.REVISE,
|
|
310
|
+
summary="Hidden fallback: failure masked as normal operation",
|
|
311
|
+
recommendation="Label degraded mode explicitly so callers know they are not getting primary behavior.",
|
|
312
|
+
evidence=(EvidenceReference(kind="probe", detail="Fallback active but not labeled as degraded mode", ok=False),),
|
|
313
|
+
repair_kind=RepairKind.REMOVE_FALLBACK.value,
|
|
314
|
+
executor_action="Add explicit degradation label when fallback is active",
|
|
315
|
+
)]
|
|
316
|
+
|
|
317
|
+
return [] # fallback used AND degradation labeled = PASS
|
|
318
|
+
|
|
319
|
+
|