From b7f1f3d05b64296204930a4d38f077c9c8ddc5c0 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 2 Oct 2026 07:17:26 +0200 Subject: [PATCH] fix(audit): the module audit checks real imports before the spec-owned list model/audit.py classified anything named in SPEC_OWNED or STANDALONE before looking at the import graph, so five modules nothing imports were reported load_bearing and the committed manifest said 0 orphans. - Reachability from the gates, the CLI and every module with a __main__ block (python -m, the plane runner) now decides the class; the lists only supply reason text, and an unreachable listed module is orphaned with its listing. - `from .pkg import sub` now records the submodule edge (plane.engine_alerts was only reachable by its SPEC_OWNED entry). - `python -m openultrasast.model.audit` regenerates the manifest. - Manifest regenerated: 145 load_bearing, 5 standalone, 5 orphaned (model.calibrate, model.execution, model.judge, slot_contract, stage_processors). The zero-orphan test is strict-xfail until they go. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- .../measurements/2026-09-08-module-audit.json | 244 +++++++++--------- src/openultrasast/model/audit.py | 75 +++++- tests/test_module_audit.py | 47 +++- 3 files changed, 235 insertions(+), 131 deletions(-) diff --git a/benchmarks/measurements/2026-09-08-module-audit.json b/benchmarks/measurements/2026-09-08-module-audit.json index fddf64b..116f2a4 100644 --- a/benchmarks/measurements/2026-09-08-module-audit.json +++ b/benchmarks/measurements/2026-09-08-module-audit.json @@ -1,24 +1,29 @@ { "modules": 155, "counts": { - "load_bearing": 148, - "standalone_capability": 7 + "load_bearing": 145, + "orphaned": 5, + "standalone_capability": 5 }, - "orphaned": [], + "orphaned": [ + "model.calibrate", + "model.execution", + "model.judge", + "slot_contract", + "stage_processors" + ], "standalone": { "fusion": "two-panel adjudication engine; reached from the scan's report stage and used standalone", "mcp": "narrow MCP server over stdio; its own `ousast mcp` entry point and OpenCode integration", "push_inputs": "pre-push-safety-net task 1.2: offline input provenance validator; python -m openultrasast.push_inputs", "push_scoring": "pre-push-safety-net task 1.3: frozen-profile diagnostic outcome scorer; python -m openultrasast.push_scoring", - "skills": "skill router; consumed by mcp and by the OpenCode integration, not by the scan path", - "slot_contract": "the slot/contract protocol stage_processors implements", - "stage_processors": "deterministic slot-contracted scan stages; pinned by the zero-dependency guard in test_gate" + "skills": "skill router; consumed by mcp and by the OpenCode integration, not by the scan path" }, "rows": [ { "module": "benchmark", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "gate", @@ -32,7 +37,7 @@ { "module": "calibration", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -48,7 +53,7 @@ { "module": "complexity", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "complexity.hints", @@ -64,7 +69,7 @@ { "module": "complexity.hints", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "complexity", "complexity.map" @@ -73,7 +78,7 @@ { "module": "complexity.ledger", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "complexity", @@ -84,8 +89,9 @@ { "module": "complexity.map", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ + "cli", "complexity", "complexity.hints", "complexity.ledger", @@ -99,7 +105,7 @@ { "module": "complexity.signals", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "complexity", "complexity.hints", @@ -109,7 +115,7 @@ { "module": "config", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "harness", @@ -206,7 +212,7 @@ { "module": "cpg.session", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cpg.backend" ] @@ -214,7 +220,7 @@ { "module": "findings", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "benchmark", "calibration", @@ -264,7 +270,7 @@ { "module": "harness", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "slot_contract" @@ -273,7 +279,7 @@ { "module": "hunter", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -281,7 +287,7 @@ { "module": "hunter_tools", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "tool_hunter" ] @@ -289,7 +295,7 @@ { "module": "improve", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "improve.evolve", @@ -300,7 +306,7 @@ { "module": "improve.evolve", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "improve", "plane.tasks.loop" @@ -309,7 +315,7 @@ { "module": "improve.journal", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "improve", "improve.evolve", @@ -320,7 +326,7 @@ { "module": "improve.memory", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "improve.evolve", @@ -330,7 +336,7 @@ { "module": "improve.validator", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "improve", "improve.evolve", @@ -341,7 +347,7 @@ { "module": "index", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "skills" @@ -350,7 +356,7 @@ { "module": "learn", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "learn.calibrate", @@ -376,7 +382,7 @@ { "module": "learn.calibrate", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.evaluate" ] @@ -384,7 +390,7 @@ { "module": "learn.cli", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -392,7 +398,7 @@ { "module": "learn.compile", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.evaluate" @@ -401,7 +407,7 @@ { "module": "learn.embeddings", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.retrieve" @@ -410,7 +416,7 @@ { "module": "learn.evaluate", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.compile" @@ -419,7 +425,7 @@ { "module": "learn.examples", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.compile", @@ -432,7 +438,7 @@ { "module": "learn.excerpt", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.embeddings", "learn.examples", @@ -443,7 +449,7 @@ { "module": "learn.features", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.excerpt", "plane.tasks.features" @@ -452,7 +458,7 @@ { "module": "learn.folds", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.compile", @@ -464,7 +470,7 @@ { "module": "learn.labels", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "learn.calibrate", @@ -480,7 +486,7 @@ { "module": "learn.leaks", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli" ] @@ -498,7 +504,7 @@ { "module": "learn.retrieve", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.evaluate", "learn.program" @@ -507,7 +513,7 @@ { "module": "learn.roles", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.features", "plane.tasks.features", @@ -517,7 +523,7 @@ { "module": "learn.schema", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.compile", "learn.evaluate", @@ -539,7 +545,7 @@ { "module": "mapping", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "gate", @@ -566,7 +572,7 @@ { "module": "model", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "cpg.backend", @@ -612,14 +618,14 @@ }, { "module": "model.calibrate", - "classification": "load_bearing", - "reason": "model-grounded-detection Req 9: the corpus as the model's calibration set", + "classification": "orphaned", + "reason": "no import path from an entry point or a python -m module; listed as: model-grounded-detection Req 9: the corpus as the model's calibration set", "importers": [] }, { "module": "model.candidates", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "model.scan" @@ -681,21 +687,21 @@ { "module": "model.evidence", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "model.scan" ] }, { "module": "model.execution", - "classification": "load_bearing", - "reason": "model-grounded-detection Req 10: the deferred execution tier seam, adopted not built", + "classification": "orphaned", + "reason": "no import path from an entry point or a python -m module; listed as: model-grounded-detection Req 10: the deferred execution tier seam, adopted not built", "importers": [] }, { "module": "model.judge", - "classification": "load_bearing", - "reason": "model-grounded-detection Req 8: one bounded question, checked against the model", + "classification": "orphaned", + "reason": "no import path from an entry point or a python -m module; listed as: model-grounded-detection Req 8: one bounded question, checked against the model", "importers": [] }, { @@ -716,7 +722,7 @@ { "module": "model.layout", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "model.partitions", "model.scan", @@ -728,7 +734,7 @@ { "module": "model.partitions", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "model.scan" ] @@ -778,7 +784,7 @@ { "module": "model.shipped", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "push.runner" @@ -813,7 +819,7 @@ { "module": "model.taxonomy", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "learn.features", @@ -836,7 +842,7 @@ { "module": "pairs", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "improve", @@ -894,7 +900,7 @@ { "module": "plane.doctor", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "plane.reconciler" ] @@ -902,7 +908,7 @@ { "module": "plane.egress", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "plane.reconciler" ] @@ -911,7 +917,9 @@ "module": "plane.engine_alerts", "classification": "load_bearing", "reason": "harnessx-removal Req 6.3: engine alerts where quick mode has no rules (PHP), on the host", - "importers": [] + "importers": [ + "cli" + ] }, { "module": "plane.generate", @@ -925,7 +933,7 @@ { "module": "plane.harvest", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -950,6 +958,7 @@ "classification": "load_bearing", "reason": "harnessx-removal Req 6.1/6.4: the memory store (file, MinIO), ingest and fact reuse; `ousast plane remember`", "importers": [ + "cli", "improve.memory", "learn.cli", "learn.compile", @@ -966,6 +975,8 @@ "classification": "load_bearing", "reason": "ai-service-plane Req 3.1-3.5, 7.3: ax-backed Run execution, status attribution and doctor; `ousast plane`", "importers": [ + "cli", + "plane.engine_alerts", "plane.generate", "plane.harvest", "plane.memory", @@ -975,7 +986,7 @@ { "module": "plane.router", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "plane.reconciler", "plane.runner" @@ -1093,7 +1104,7 @@ { "module": "policy", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "findings", @@ -1109,7 +1120,7 @@ { "module": "policy.verycode", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "policy" ] @@ -1117,7 +1128,7 @@ { "module": "preprocess", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "complexity.hints", @@ -1153,7 +1164,7 @@ { "module": "profiling", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -1161,7 +1172,7 @@ { "module": "provenance", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -1169,7 +1180,7 @@ { "module": "provider", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.compile", @@ -1181,7 +1192,7 @@ { "module": "provider.openrouter", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.cli", "learn.compile", @@ -1209,7 +1220,7 @@ { "module": "push.assistance", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "push.runner" ] @@ -1240,7 +1251,7 @@ { "module": "push.eligibility", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "push.runner" ] @@ -1268,7 +1279,7 @@ { "module": "push.reuse", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "push.runner" ] @@ -1276,7 +1287,7 @@ { "module": "push.runner", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "push.assistance" @@ -1293,7 +1304,7 @@ { "module": "push.vetoes", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "push.runner" ] @@ -1315,7 +1326,7 @@ { "module": "rank", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "calibration", "cli", @@ -1333,7 +1344,7 @@ { "module": "redaction", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "harness", "model.judge", @@ -1347,7 +1358,7 @@ { "module": "regress", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "regress.candidate" @@ -1356,7 +1367,7 @@ { "module": "regress.candidate", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "regress" ] @@ -1364,7 +1375,7 @@ { "module": "regress.recipes", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "regress", "regress.candidate" @@ -1373,7 +1384,7 @@ { "module": "regress.safety", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "regress", "regress.candidate" @@ -1382,7 +1393,7 @@ { "module": "regress.verdict", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "regress", "regress.candidate" @@ -1391,7 +1402,7 @@ { "module": "reports", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -1399,7 +1410,7 @@ { "module": "repos", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "push_inputs" @@ -1408,7 +1419,7 @@ { "module": "ruleset", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "findings", @@ -1431,7 +1442,7 @@ { "module": "ruleset.frameworks", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "learn.labels", "learn.roles", @@ -1442,7 +1453,7 @@ { "module": "ruleset.store", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "improve.validator", "ruleset" @@ -1451,7 +1462,7 @@ { "module": "run", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "reports" @@ -1460,7 +1471,7 @@ { "module": "sandbox", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "regress.candidate", @@ -1472,7 +1483,7 @@ { "module": "sandbox.probe", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "sandbox" ] @@ -1480,7 +1491,7 @@ { "module": "sandbox.runner", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "regress.candidate", "regress.recipes", @@ -1491,7 +1502,7 @@ { "module": "scoring", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "improve.evolve", @@ -1501,7 +1512,7 @@ { "module": "scoring.project_score", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "scoring" ] @@ -1509,7 +1520,7 @@ { "module": "semantic", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "cpg.capability", @@ -1538,7 +1549,7 @@ { "module": "semantic.cst", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic.ir" ] @@ -1546,7 +1557,7 @@ { "module": "semantic.engines", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cpg.capability", "semantic", @@ -1556,7 +1567,7 @@ { "module": "semantic.extra", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic", "semantic.cst", @@ -1566,7 +1577,7 @@ { "module": "semantic.facts", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "hunter_tools", @@ -1590,7 +1601,7 @@ { "module": "semantic.functions", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "hunter_tools", "pairs", @@ -1601,7 +1612,7 @@ { "module": "semantic.ir", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "hunter_tools", @@ -1619,7 +1630,7 @@ { "module": "semantic.obligations", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "hunter_tools", @@ -1638,7 +1649,7 @@ { "module": "semantic.obligations.check", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "semantic.obligations", @@ -1648,7 +1659,7 @@ { "module": "semantic.obligations.dominance", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "hunter_tools", @@ -1659,7 +1670,7 @@ { "module": "semantic.obligations.facts", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "model.specs", "pairs", @@ -1674,7 +1685,7 @@ { "module": "semantic.obligations.intent", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -1682,7 +1693,7 @@ { "module": "semantic.obligations.operations", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "model.candidates", "semantic.obligations.check", @@ -1693,7 +1704,7 @@ { "module": "semantic.obligations.policy", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli", "semantic.obligations.check" @@ -1702,7 +1713,7 @@ { "module": "semantic.obligations.shapes", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic.obligations", "semantic.obligations.operations" @@ -1711,7 +1722,7 @@ { "module": "semantic.obligations.siblings", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic.obligations.check" ] @@ -1719,7 +1730,7 @@ { "module": "semantic.overlay", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic", "semantic.prove_filter" @@ -1728,7 +1739,7 @@ { "module": "semantic.prove_filter", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic" ] @@ -1736,7 +1747,7 @@ { "module": "semantic.taint", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "semantic.overlay" ] @@ -1751,22 +1762,22 @@ }, { "module": "slot_contract", - "classification": "standalone_capability", - "reason": "the slot/contract protocol stage_processors implements", + "classification": "orphaned", + "reason": "no import path from an entry point or a python -m module (imported only by unreachable stage_processors); listed as: the slot/contract protocol stage_processors implements", "importers": [ "stage_processors" ] }, { "module": "stage_processors", - "classification": "standalone_capability", - "reason": "deterministic slot-contracted scan stages; pinned by the zero-dependency guard in test_gate", + "classification": "orphaned", + "reason": "no import path from an entry point or a python -m module; listed as: deterministic slot-contracted scan stages; pinned by the zero-dependency guard in test_gate", "importers": [] }, { "module": "stages", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "cli" ] @@ -1774,8 +1785,9 @@ { "module": "tool_hunter", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ + "cli", "model.endpoint", "plane.budget", "plane.tasks.roles", @@ -1785,7 +1797,7 @@ { "module": "verification", "classification": "load_bearing", - "reason": "reachable from gate, map_gate, pair_gate or the CLI", + "reason": "reachable from gate, map_gate, pair_gate, cli", "importers": [ "calibration", "cli", diff --git a/src/openultrasast/model/audit.py b/src/openultrasast/model/audit.py index dbbf5d0..3449d68 100644 --- a/src/openultrasast/model/audit.py +++ b/src/openultrasast/model/audit.py @@ -6,9 +6,12 @@ checkable rather than a thing someone notices two specs later. A module is: - load_bearing reachable from a gate, the scan CLI, or a surviving requirement - standalone_capability its own entry point and users, not on the scan path - orphaned no importer, no entry point, no requirement — it should not be in the tree + load_bearing reachable by imports from a gate, the CLI, or a module run with ``python -m`` + standalone_capability reachable the same way, but its own entry point and users, not on the scan path + orphaned not reachable from any of those -- it should not be in the tree + +Reachability decides the class. ``SPEC_OWNED`` and ``STANDALONE`` only supply the reason text: being named +by a spec does not keep a module nobody imports alive. Pure stdlib, no imports of the package it audits: it reads the import graph with ``ast`` so a broken module is still classifiable. @@ -26,7 +29,7 @@ PACKAGE = "openultrasast" # Entry points a module can be reachable from. The three gates are the contract; the CLI is the product. ROOTS = ("gate", "map_gate", "pair_gate", "cli", "__main__") -# Modules that stand alone: their own CLI subcommand or external protocol, deliberately off the scan path. +# Reason text for reachable modules, by the requirement that owns them. Not a classification: see audit(). SPEC_OWNED = { "push.cache": "pre-push-safety-net task 6.2: bounded artifact publication and leased eviction; runner integration in 6.4", "push.report": "pre-push-safety-net Req 5.1/5.2/5.5/7.3: compact output and complete artifacts; runner integration pending", @@ -81,6 +84,7 @@ SPEC_OWNED = { "plane.runner": "ai-service-plane Req 4.1-4.4: the ax-task-runner entrypoint (PID 1 of the task image); delivers the output directory", } +# Reachable modules that stand alone: their own CLI subcommand or external protocol, off the scan path. STANDALONE = { "push_scoring": "pre-push-safety-net task 1.3: frozen-profile diagnostic outcome scorer; python -m openultrasast.push_scoring", "push_inputs": "pre-push-safety-net task 1.2: offline input provenance validator; python -m openultrasast.push_inputs", @@ -140,6 +144,10 @@ def import_graph(root: Path) -> dict[str, set[str]]: continue if target: imported.add(target) + # `from .plane import engine_alerts` imports the submodule `plane.engine_alerts`, not only the + # package; names that are not modules are dropped later because they are not in the graph. + prefix = f"{target}." if target else "" + imported.update(f"{prefix}{alias.name}" for alias in node.names if alias.name != "*") elif isinstance(node, ast.Import): for alias in node.names: if alias.name.startswith(f"{PACKAGE}."): @@ -148,6 +156,28 @@ def import_graph(root: Path) -> dict[str, set[str]]: return graph +def main_modules(root: Path) -> set[str]: + """Modules with a ``__main__`` block: run by name (``python -m``, the plane runner), so they are entry points.""" + found: set[str] = set() + for path in sorted(root.rglob("*.py")): + if "__pycache__" in path.parts: + continue + try: + tree = ast.parse(path.read_text()) + except (OSError, SyntaxError): + continue + for node in tree.body: + if ( + isinstance(node, ast.If) + and isinstance(node.test, ast.Compare) + and isinstance(node.test.left, ast.Name) + and node.test.left.id == "__name__" + and any(isinstance(c, ast.Constant) and c.value == "__main__" for c in node.test.comparators) + ): + found.add(module_name(path, root)) + return found + + def _reachable(graph: Mapping[str, set[str]], roots: Iterable[str]) -> set[str]: seen: set[str] = set() stack = [root for root in roots if root in graph] @@ -173,24 +203,33 @@ def audit(root: Path) -> tuple[ModuleAudit, ...]: for candidate in (target, target.rsplit(".", 1)[0]): if candidate in importers and candidate != name: importers[candidate].add(name) - live = _reachable(graph, ROOTS) + run_by_name = main_modules(root) + live = _reachable(graph, (*ROOTS, *sorted(run_by_name))) results: list[ModuleAudit] = [] for name in sorted(graph): if not name: # the package __init__ itself continue who = tuple(sorted(importers[name])) - if name in SPEC_OWNED: + listed = SPEC_OWNED.get(name) or STANDALONE.get(name) + # Reachability decides; the lists only supply reason text. A listed module nothing reaches is still + # orphaned -- the list is what hid five dead modules behind "load_bearing" until 2026-10-02. + if name not in live: + reason = "no import path from an entry point or a python -m module" + if who: + reason += f" (imported only by unreachable {', '.join(who)})" + if listed: + reason += f"; listed as: {listed}" + results.append(ModuleAudit(name, "orphaned", reason, who)) + elif name in STANDALONE: + results.append(ModuleAudit(name, "standalone_capability", STANDALONE[name], who)) + elif name in SPEC_OWNED: results.append(ModuleAudit(name, "load_bearing", SPEC_OWNED[name], who)) elif name in ROOTS: results.append(ModuleAudit(name, "load_bearing", "entry point", who)) - elif name in STANDALONE: - results.append(ModuleAudit(name, "standalone_capability", STANDALONE[name], who)) - elif name in live: - results.append(ModuleAudit(name, "load_bearing", f"reachable from {', '.join(ROOTS[:3])} or the CLI", who)) - elif who: - results.append(ModuleAudit(name, "load_bearing", f"imported by {', '.join(who)}", who)) + elif name in run_by_name: + results.append(ModuleAudit(name, "load_bearing", f"python -m openultrasast.{name}", who)) else: - results.append(ModuleAudit(name, "orphaned", "no importer, no entry point, no surviving requirement", who)) + results.append(ModuleAudit(name, "load_bearing", f"reachable from {', '.join(ROOTS[:4])}", who)) return tuple(results) @@ -211,4 +250,12 @@ def to_dict(results: Iterable[ModuleAudit]) -> dict[str, object]: } -__all__ = ["ModuleAudit", "SPEC_OWNED", "STANDALONE", "audit", "import_graph", "module_name", "to_dict"] +__all__ = ["ModuleAudit", "SPEC_OWNED", "STANDALONE", "audit", "import_graph", "main_modules", "module_name", "to_dict"] + + +if __name__ == "__main__": # python -m openultrasast.model.audit [SRC] -- the manifest's regeneration command + import json + import sys + + source = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[1] + print(json.dumps(to_dict(audit(source)), indent=2)) diff --git a/tests/test_module_audit.py b/tests/test_module_audit.py index bb94810..0b2ab73 100644 --- a/tests/test_module_audit.py +++ b/tests/test_module_audit.py @@ -5,10 +5,13 @@ from __future__ import annotations import json from pathlib import Path +import pytest + SRC = Path("src/openultrasast") MANIFEST = Path("benchmarks/measurements/2026-09-08-module-audit.json") +@pytest.mark.xfail(strict=True, reason="five dead modules the fixed audit found; removed in the next commit") def test_no_module_in_the_tree_is_orphaned() -> None: from openultrasast.model.audit import audit @@ -44,4 +47,46 @@ def test_the_manifest_matches_the_tree() -> None: committed = json.loads(MANIFEST.read_text()) current = to_dict(audit(SRC)) assert committed["counts"] == current["counts"], "the tree drifted from its committed audit" - assert committed["orphaned"] == current["orphaned"] == [] + assert committed["orphaned"] == current["orphaned"] + + +def _tree(root: Path, files: dict[str, str]) -> Path: + for name, text in files.items(): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + return root + + +def test_a_spec_listed_module_nothing_imports_is_still_orphaned(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Reachability decides; SPEC_OWNED and STANDALONE only supply the reason (the list hid five dead modules).""" + from openultrasast.model import audit as module + + src = _tree(tmp_path, {"cli.py": "from . import used\n", "used.py": "", "ghost.py": "", "lonely.py": ""}) + monkeypatch.setitem(module.SPEC_OWNED, "ghost", "some spec Req 1: still listed") + monkeypatch.setitem(module.STANDALONE, "lonely", "its own entry point, supposedly") + rows = {row.module: row for row in module.audit(src)} + assert rows["used"].classification == "load_bearing" + assert rows["ghost"].classification == "orphaned" + assert "still listed" in rows["ghost"].reason + assert rows["lonely"].classification == "orphaned" + + +def test_submodule_imports_and_python_m_modules_count_as_reachable(tmp_path: Path) -> None: + from openultrasast.model.audit import audit + + src = _tree( + tmp_path, + { + "cli.py": "def run():\n from .pkg import sub\n", + "pkg/__init__.py": "", + "pkg/sub.py": "", + "tool.py": 'from . import helper\n\nif __name__ == "__main__":\n pass\n', + "helper.py": "", + "dead.py": "from . import deader\n", + "deader.py": "", + }, + ) + rows = {row.module: row.classification for row in audit(src)} + assert rows["pkg.sub"] == rows["tool"] == rows["helper"] == "load_bearing" + assert rows["dead"] == rows["deader"] == "orphaned"