Repository navigation
refactor(slm): split four frozen modules so their ceilings stop blocking work (#15495, #15496) - #16099
Conversation
…ing work (#15495, #15496) Four of the modules #15495 needs to touch sit at exactly their recorded size ceiling, and the ratchet says what that means: models/database.py: 1231 lines, over its recorded ceiling of 1229. A grandfathered file may not grow (#14236) -- the exemption freezes the size it was granted for, it does not license more. scripts/python_file_size_known_large.py gives the only sanctioned response: "Never add an entry to make a new file pass; split the file instead." Every one of the last fifteen commits to that baseline is a decrease, so this is enforced practice, not aspiration. This commit does the splitting; no behaviour changes and no feature lands here. agent.py 739 -> 675 payload builders -> slm/agent/heartbeat_payload.py schemas.py 2335 -> 2181 NPU worker block -> models/npu_schemas.py database.py 1229 -> 1135 status enums -> models/enums.py nodes.py 3017 -> 2927 SSH test helpers -> api/node_ssh_helpers.py Each block moved verbatim. Boundaries were chosen where the code was already separable rather than where the line count happened to work out: - The three heartbeat builders needed no agent state beyond what is now passed explicitly, which is what made them safe to lift out as free functions. - The NPU block was banner-delimited and referenced nowhere else in schemas.py -- zero remaining NPU references there after the move. - Only the seven enums that preceded the first mapped class moved. The twelve further down database.py stay: each sits beside the model whose column it constrains, and separating them would cost more than it frees. - The five SSH helpers build or read an ssh/sshpass invocation and touch no router, session or model state. models.database and models.schemas keep re-exporting the moved names, so all 67 modules importing from them are untouched; only api/npu.py changes an import, because its symbols moved to a module that did not previously exist. Both baselines are lowered in the same commit, as scripts/python_file_size_known_large.py requires -- an unlowered ceiling re-licenses the lines just cut, and the audit fails on that too, not only on growth. Verification: - scripts/check_python_file_size.py --audit-ceilings: 5295 files walked, 0 problems - pipeline-scripts/detect-agent-code-drift.sh: 12 pairs byte-identical - every moved symbol resolves to exactly one definition; the CodeStatus and RoleStatus duplicates the sweep found are pre-existing and unrelated (a test-local stub and role_detector.py's dataclass of the same name) - all four new modules and every edited file parse The new agent module is deployed by its own copy: task in roles/slm_agent/tasks/main.yml. That list is hand-maintained, one task per module, and nothing checks it against the directory -- a new module that is mirrored but not shipped would fail only at runtime on a node. Filed separately rather than fixed here to keep this commit a pure move.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Filed the deploy-list gap I flagged in the PR body as #16100 — a guard that the Worth naming why it belongs with #13148 and #15684 rather than beside them: the drift check for this exact directory pair went green while |
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
…d they go (#15495) The migration gate went red on the first push: File "autobot-slm-backend/models/database.py", line 32, in <module> from .enums import ( # noqa: F401 ImportError: attempted relative import with no known parent package Three steps in .github/workflows/slm-migration-gate.yml load models/database.py with spec_from_file_location to run Base.metadata.create_all the way production does. Loaded that way the module has no parent package, and base's database.py imported nothing from models/ at all -- the by-path load was package-free, and this commit's parent was the first change to break that. Switching to `from models.enums import` fixes the relative-import error and walks into the next one: importing anything from the models package runs models/__init__.py, which pulls in user_management and thence autobot_shared, which that loader does not have. Both of which models/database.py:302 already says, and I edited past it: # ServiceStatus moved to top-level service_status.py (#16019) -- this file is # AT its ceiling, and models/ would drag in autobot_shared. See that docstring. from service_status import ServiceStatus # noqa: E402,F401 service_status.py's docstring names this failure by name -- "broke `SLM migration runner - Postgres 16` with ModuleNotFoundError: No module named 'autobot_shared'". Same wall, same file, second time. The rule I broke is check the decisions recorded outside the file before editing it: the constraint was written down at the exact line I was editing, and the ceiling I was working around is the same ceiling that put it there. So the enums follow the established route: top-level status_enums.py, re-exported from models.database, with the reason recorded at both ends so a later tidy-up into models/ has to argue with it first. models/enums.py -> status_enums.py (rename, contents unchanged) models/database.py 1135 -> 1141 (the import comment explaining why) Verification -- the gate's exact construction, run locally: spec_from_file_location("_slm_models_database_14326", "models/database.py") -> OK, loaded by path with no package: 33 tables -> enums reachable via re-export: NodeStatus, CodeStatus.UNKNOWN 33 mapped classes on base and 33 on this branch: enums are not mapped, so the schema surface cannot have moved, and it did not. ratchet audit: 5297 files walked, 0 problems agent drift: 12 pairs byte-identical
|
Migration gate went red and is fixed — the correction matters more than the failure. First push: Three steps in Switching to Both of which # ServiceStatus moved to top-level service_status.py (#16019) -- this file is
# AT its ceiling, and models/ would drag in autobot_shared. See that docstring.
from service_status import ServiceStatus # noqa: E402,F401And Fixed by following the established route rather than inventing a third one: Verified by running the gate's exact construction locally, not by reasoning about it: 33 mapped classes on base, 33 on this branch — enums are not mapped, so the schema surface could not have moved, and did not. Ratchet 5297/0, drift 12 pairs. The other three extractions were unaffected: |
…lit-frozen-modules
…mment (#15495) models/database.py:302 has said this since #16019: # ServiceStatus moved to top-level service_status.py (#16019) -- this file is # AT its ceiling, and models/ would drag in autobot_shared. See that docstring. and service_status.py's docstring names the failing job by name. Both were on base while the parent commit of this one broke the migration gate in exactly the way they describe, from an edit twenty lines away. The knowledge was in the best place it could be -- the file being changed, at the point of the change -- and it was read past twice in two months. Prose is not a guard. The predicate is measured, not asserted. Two things had to be established before this could be a test rather than a hunch: 1. Which loads are strict. The workflow has two styles. A dotted module name ("models.database") means the step scaffolded a parent package in sys.modules first, and package imports resolve. A name with no dot ("_slm_models_database_14326") has no package behind it. Only the second kind is constrained, and the targets are derived from the workflow rather than listed here. 2. Which imports actually fail there. My first draft flagged every first-party package import and failed on base: migrations/runner.py is loaded the same strict way and imports `from migrations import utils` and `from user_management.config import ...` quite happily. The real rule is narrower -- an import fails only if that package's __init__ chain reaches autobot_shared, which the job does not install. Walking the tree: models REACHES autobot_shared via user_management.models.user middleware REACHES autobot_shared via monitoring.prometheus_metrics monitoring REACHES autobot_shared api, migrations, services, slm, user_management clean That matches the observed CI behaviour exactly, which the blanket version did not. Verified by extracting the test's own functions and running them against the tree: targets: migrations/runner.py, models/database.py -> both CLEAN attempt 1 from .enums import NodeStatus -> relative import: .enums attempt 2 from models.enums import ... -> unreachable package the fix from status_enums import ... -> CLEAN runner's own from migrations import utils -> CLEAN So it fails on both of the two things I actually did, and passes everything that legitimately works there. Two controls, because a matcher that flags nothing is indistinguishable from a clean tree: the derivation must keep finding models/database.py among the strict targets, and the reachability walk must keep finding `models` among the packages that reach autobot_shared. If either stops, the guard broke rather than the tree improving.
Black -- 3 files, one cause. Every extraction seam left an extra blank line; the PostToolUse auto-format never fired because these edits were made by script rather than through the editing tool. npu_schemas.py, nodes.py, database.py. Baselines follow the 4 lines out: database.py 1141 -> 1140, nodes.py 2927 -> 2926. shard 6/12 -- repo_tests/enum_union_guard_test.py anchors to models/database.py by path for BackupServiceType and _BACKUP_SERVICE_TYPE_ALIASES, which moved to status_enums.py. Repointed. Kept to one line so the file stays at its recorded 801: a grandfathered file may not grow, and a four-line assertion message is not worth a ceiling. This one is the same class I filed as #16101 an hour ago -- and my own sweep for it MISSED this instance. The narrowed detector required the anchor literal on the same expression (`Path(__file__).parent / "nodes.py"`); this composes from a module-level `SLM` variable, so `SLM / "models" / "database.py"` never matched. The broad 136-hit pass had it; narrowing to 87 dropped it, and the single positive control survived because it happened to be of the shape that survived. One control is a control for one shape. shard 12/12 -- python_filter_covers_its_guards_test.py, and this one is pointed: the guard I added last commit reads .github/workflows/slm-migration-gate.yml by concrete literal, and .github/filters/python-paths.yml does not cover that file. So adding a fourth by-path load -- exactly the change the guard exists for -- would have taken the required-context shim's green with the guard never running. A guard against "the constraint is not enforced" that is itself unreachable by the filter. Widened the filter by one entry rather than recording an exemption: MAX_UNCOVERED_READS stays 39, since the rule is that the record only shrinks. The comment two lines above my entry describes the same detector blind spot from the other side -- it recognises `NAME / "a" / "b"` and misses `parents[1]`, `.parent`, `project_root()`. Mine recognised the roots it missed and missed the composition it catches. Verification: black clean on every file touched; ratchet 5299 walked, 0 problems; agent drift 12 pairs byte-identical. Not fixed here: `Check PR links to its issue`. The body carries `Refs #15495, #15496` and satisfies both (a) the keyword and (b) the standalone token for 15495. Re-checking that separately rather than editing the body again on the assumption it is wrong.
CI diagnosis —
|
Both causes measured1.
|
| Duplicated lines | 4729 |
| Total lines | 259798 |
| Measured | 1.8203 % |
SLM_THRESHOLD |
1.82 % — identical on base and on this branch |
| Lines allowed | 4728.3 |
| Over by | 0.7 lines · 0.0003 pp |
The threshold is not the problem and this branch did not move it — I diffed
.github/workflows/duplication-guard.yml on both refs and SLM_THRESHOLD: '1.82'
is unchanged. The guard is correct: splitting four frozen modules added
duplication, and the scope had essentially zero headroom.
Why the log makes this look like a tie
ERROR: jscpd found too many duplicates (1.8%) over threshold (1.8%)
Both values print rounded to 1 dp, so a 0.0003 pp overage renders as 1.8 vs 1.8
— two identical numbers with a failure between them. The real comparison is
1.8203 > 1.82. Anyone reading only that line would reasonably conclude the guard
had misfired.
Options
- Extract the shared code the split introduced — genuinely reduces duplication.
- Re-pin
SLM_THRESHOLDwith the documented reason the workflow asks for. Note
the pinning instruction says rounded UP to 2 dp; 1.8203 rounds up to 1.83,
which is what a re-pin should use, not 1.82 again.
Option 1 is the right one unless the duplication is genuinely unavoidable — a re-pin
buys 0.7 lines of headroom and the next refactor trips it again.
|
Failing step is Nothing else fails — Mechanical: Flagging rather than fixing — the session that opened this PR is no longer running, and |
…py (#15495) The code-quality check failed at step 6, isort, on one file: autobot-slm-backend/api/nodes.py Imports are incorrectly sorted and/or formatted `from autobot_shared.ssot_config import config` sat above the `from api.node_ssh_helpers import (...)` block. With the profile and known_first_party from pyproject.toml, first-party ordering is api < autobot_shared < models, so the single import moves below that block. Pure reorder, one line moved. The file is 2926 lines before and after, so the size ratchet this PR shrinks is untouched and no baseline is regenerated. Verified with the exact CI invocation rather than bare isort, since --settings-path=. is what supplies the profile and src_paths: python3 -m isort --check-only --settings-path=. --line-length=120 \ autobot-backend/ autobot-slm-backend/ autobot_shared/ -> clean black --check on the same file also passes, so the two formatters agree.
Thinking Path
I picked up #15495 (per-node LLM capability probe) and #15496 (llm_mode + preset resolver) and got
blocked before the first edit. Every module the pair needs to touch sits at exactly its recorded
size ceiling, and the ratchet is explicit about what that means:
scripts/python_file_size_known_large.pygives exactly one sanctioned response: "Never add anentry to make a new file pass; split the file instead." I checked whether that is enforced or
merely documented before acting on it — every one of the last fifteen commits to that baseline is
a decrease, and the audit runs as a required check in
code-quality.yml:654. It is enforced.So this PR is the splitting, and nothing else. No feature lands here. #15495/#15496 follow in a
second PR that rides the headroom this creates, which keeps each half inside one honest review pass.
Boundaries were chosen where the code was already separable, not where the line count worked out:
what made them safe to lift out as free functions, and it is why the move is provably behaviour-free.
schemas.py— zero remainingNPUreferences there after the move.database.pystay: each sits beside the model whose column it constrains. Moving them would buylines and cost cohesion, and "why these seven and not those twelve" has a real answer rather than
a convenient one.
ssh/sshpassinvocation and touch no router, session ormodel state.
What Changed
slm/agent/agent.pyslm/agent/heartbeat_payload.pymodels/schemas.pymodels/npu_schemas.pymodels/database.pymodels/enums.pyapi/nodes.pyapi/node_ssh_helpers.pyEvery block moved verbatim.
models.databaseandmodels.schemasre-export the moved names, soall 67 modules importing from them are untouched; only
api/npu.pychanges an import, because itssymbols moved to a module that did not previously exist.
Both baselines (
scripts/python_file_size_known_large.pyandrepo_tests/python_file_size_ratchet_baseline.py) are lowered in the same commit. That is requiredin both directions — the audit fails on an unlowered ceiling too, not only on growth, because an
unlowered ceiling re-licenses the lines just cut.
slm/agent/heartbeat_payload.pyis mirrored to the ansible copy and has its owncopy:task inroles/slm_agent/tasks/main.yml.Verification
scripts/check_python_file_size.py --audit-ceilings— 5295 files walked, 0 problemspipeline-scripts/detect-agent-code-drift.sh— 12 pairs byte-identicalCodeStatus×2 andRoleStatus×3; both are pre-existing and unrelated — a test-local stub intest_node_update_availability_11964.pyandrole_detector.py's dataclass of the same name.The pre-push hook caught a genuine break and it is worth recording.
api/nodes_test.pyAST-extracts the two SSH argv builders from
nodes.pyby path rather than importing them, somoving them made it find zero. Its own assertion message is
"Rename tracking broke."— the guardwas written to fail loudly on exactly this, and it did. Fixed by repointing it at
node_ssh_helpers.py(second commit).I deliberately did not take the simplification that move now allows. The AST-extraction exists
because
nodes.pyregisters FastAPI routes at import time;node_ssh_helpers.pydoes not, so thetest could import it directly. But the helper module imports
models.schemas, which this suitestubs with MagicMock Pydantic, so switching to a real import is a behaviour change I cannot verify
from here. Repointing is guaranteed-equivalent; the simplification belongs in its own change.
One thing I found and did not fix here, to keep this a pure move: the agent module deploy list
in
roles/slm_agent/tasks/main.ymlis hand-maintained, onecopy:task per module, and nothingchecks it against the directory. A new module that is mirrored but not shipped would fail only at
runtime on a node. I verified all 7 production modules are currently covered. Filing separately —
it is the same defect class as #13148 and #15684.
Model Used
Claude Opus 5
Refs #15495, #15496 — deliberately
Refs, notCloses. This PR delivers neither issue;it removes the size ceilings that block both, and the feature lands in a follow-up.
It also carries two things the split paid for and should not lose: the fix for the migration
gate red (
status_enums.pytop-level, per #16019's recorded route) andrepo_tests/by_path_loaded_modules_are_package_free_test.py, which turns that twice-hitconstraint from a comment into a pre-push failure. Related: #16100, #16101.