diff --git a/gitgalaxy/security/security_auditor.py b/gitgalaxy/security/security_auditor.py index abed1c583..8e97a17ec 100644 --- a/gitgalaxy/security/security_auditor.py +++ b/gitgalaxy/security/security_auditor.py @@ -382,7 +382,22 @@ def _construct_feature_matrix(self, artifacts): "log_max_func_complexity": np.log1p(np.maximum(max_func_comp, 0)), "log_avg_func_args": np.log1p(np.maximum(avg_func_args, 0)), "func_complexity_gini": float(tel.get("func_complexity_gini", 0.0)), - "func_internal_density": float(tel.get("func_internal_density", 0.0)), + # func_internal_density (#2714): a permanent 0.0 placeholder, + # not a live feature. No telemetry writer has ever emitted + # this key -- signal_processor's telemetry_payload, the + # galaxyscope augmentation block and the ecosystem pass all + # skip it, and the density is computed only inside + # record_keeper.py (~L496) while the file_data row is + # written. The old tel.get(...) read therefore took its 0.0 + # default for every file, every scan, exactly like the + # prompt_injection/agentic_rce placeholders documented in + # analysis_lens.py. The column is frozen rather than deleted + # because audit_repository aligns the frame by name via + # df.reindex(columns=self.feature_names, fill_value=np.nan): + # dropping it would feed a trained model NaN where it has + # always seen 0.0. Populating it for real is a scored-model + # input change that needs a retrain, not this fix. + "func_internal_density": 0.0, "orphaned_logic": float(hit_dict.get("orphaned_logic", 0)), "duplicate_logic": float(hit_dict.get("duplicate_logic", 0)), "log_direct_upstream": np.log1p(np.maximum(dep.get("direct_upstream", 0), 0)), diff --git a/tests/ruff_audit_baseline.json b/tests/ruff_audit_baseline.json index 2a62db1f2..213f921c2 100644 --- a/tests/ruff_audit_baseline.json +++ b/tests/ruff_audit_baseline.json @@ -44,7 +44,7 @@ "gitgalaxy/recorders/record_keeper.py:965: W291": "Trailing whitespace", "gitgalaxy/recorders/sbom_recorder.py:221: PERF401": "Use `list.extend` to create a transformed list", "gitgalaxy/security/security_auditor.py:359: RUF046": "Value being cast to `int` is already an integer", - "gitgalaxy/security/security_auditor.py:426: PERF203": "`try`-`except` within a loop incurs performance overhead", + "gitgalaxy/security/security_auditor.py:441: PERF203": "`try`-`except` within a loop incurs performance overhead", "gitgalaxy/security/security_lens.py:443: SIM102": "Use a single `if` statement instead of nested `if` statements", "gitgalaxy/standards/config_resolver.py:254: UP045": "Use `X | None` for type annotations", "gitgalaxy/standards/config_resolver.py:255: UP045": "Use `X | None` for type annotations", diff --git a/tests/security_auditing/test_security_auditor.py b/tests/security_auditing/test_security_auditor.py index 8ca7ab5a1..0eb3158a5 100644 --- a/tests/security_auditing/test_security_auditor.py +++ b/tests/security_auditing/test_security_auditor.py @@ -93,6 +93,29 @@ def test_construct_feature_matrix(mock_artifacts): assert "log_logic_loc" in df.columns +def test_func_internal_density_is_a_frozen_placeholder(mock_artifacts): + """#2714: the frame's func_internal_density column is a permanent 0.0. + + Nothing ever writes that key into artifact telemetry -- the density is + computed only inside record_keeper.py while the file_data row is written -- + so the column has been a constant 0.0 in every frame the model was ever + scored or trained against. Making it vary is a scored-model input change + that needs a retrain, so this pins the constant: if some future pass does + start emitting the telemetry key, the security frame must not silently + start moving with it. + """ + auditor = SecurityAuditor() + auditor.SIGNAL_SCHEMA = ["high_risk_execution", "io", "state_mutation", "safety", "dead_code"] + + mock_artifacts[0]["telemetry"]["func_internal_density"] = 0.87 + + auditor._resolve_dependency_graph(mock_artifacts) + df = auditor._construct_feature_matrix(mock_artifacts) + + assert "func_internal_density" in df.columns + assert (df["func_internal_density"] == 0.0).all() + + def test_construct_feature_matrix_exception_fallback(): """Proves a corrupted artifact payload generates a safe, empty fallback row.""" auditor = SecurityAuditor()