SPDX-License-Identifier: AGPL-3.0-or-later¶
Commercial license available¶
(c) Concepts 1996-2026 Miroslav Sotek. All rights reserved.¶
(c) Code 2020-2026 Miroslav Sotek. All rights reserved.¶
ORCID: 0009-0009-3560-0851¶
Contact: www.anulum.li | protoscience@anulum.li¶
SCPN Phase Orchestrator - Module size policy¶
Module size policy¶
Line count is a proxy, never the gate. Single-responsibility code is fine at any length; multi-responsibility code must be split at any length. The number is a trigger for a review, not a budget to satisfy.
The real criterion¶
One module = one responsibility. If you need the word "and" to describe what a module does, split it.
The decisive test is the AST call-graph, not the line count:
- Split it when the module decomposes into a few cohesive clusters with only a handful of cross-edges — i.e. an acyclic dependency graph of sub-modules. This is a god-module: the responsibilities are merely co-located.
- Keep it whole when the module is one densely interconnected cluster — a solver, parser, state machine, or single class whose methods share state. Splitting it would invent artificial coupling and import ceremony; if such a class is itself doing too much, the fix is to extract collaborators, not to scatter its code across files.
Trigger thresholds (tools/check_module_size.py)¶
| Lines | Action |
|---|---|
| < 600 | Nothing — this matches the size of a healthy single-responsibility module. |
| 600–900 | Fine if single-responsibility; a glance is enough. |
| 900–1199 | Review trigger. Run the AST call-graph: does it split into clean clusters? |
| ≥ 1200 | Strong multi-responsibility signal. Default-split unless the call-graph shows one cohesive cluster; if kept whole, record it in the allowlist with a justification. |
These numbers are calibrated to this repository. The god-file refactor campaign
split ten modules in the 1185–1882 range, and every one was genuinely
multi-responsibility — it decomposed into 4–9 single-responsibility sub-modules
with a clean DAG and byte-identical (AST-level) relocation. The resulting
sub-modules cluster around 100–620 lines, with the largest cohesive units
(boundary 586, runtime 620, compiler 451) comfortable as single
responsibilities. So in practice, ≥ 1200 lines reliably meant "split me", while
the 900–1199 band is the judgement zone.
Exemptions¶
- Test modules and generated trees (
grpc_gen) are exempt — their size is not a design choice. - A package
__init__.pythat is a thin re-export façade is exempt by intent.
The guard¶
tools/check_module_size.py reports modules by tier:
- Default (
python tools/check_module_size.py) prints the report and exits 0 — a warning, not an error. - Ratchet (
python tools/check_module_size.py --check) exits non-zero only when a module is ≥ 1200 lines and absent fromtools/large_module_allowlist.json. New god-modules are blocked; modules reviewed and kept whole are grandfathered with a recorded reason.
To keep a module above the split threshold, add an entry to the allowlist:
{
"reviewed_large_modules": [
{"module": "scpn_phase_orchestrator.pkg.solver", "reason": "one cohesive solver; methods share state"}
]
}
The reason is mandatory — an allowlist entry is a documented design decision, not a silence switch.
Standing record (for reviewers)¶
File size in this repository is an actively audited design decision, not an oversight. Two facts establish that:
-
The original god-file campaign is complete. Every earlier module that exceeded the split threshold was decomposed into a responsibility-scoped package —
runtime/cli,studio/ui_helpers,nn/supervisor,plugins/registry,binding/digital_twin,supervisor/formal_export,supervisor/hierarchy,runtime/cli/_payloads,binding/semantic, andmonitor/stl. Each split was derived from an AST call-graph, produced a clean acyclic module graph, and relocated every node byte-identically at the AST level (proven per node). The commits are in the git history. Later modules crossing the threshold require a fresh review and an explicit allowlist decision below. -
The largest remaining modules were reviewed and kept by responsibility. The figures below were current at the last review; run
python tools/check_module_size.pyfor live numbers. Each sits in the 900–1199 review band and was assessed with the AST call-graph and found to be a single responsibility — splitting it would invent coupling, not remove it.
| Module | ~Lines | Why it is one responsibility |
|---|---|---|
upde/moving_frame |
~1120 | One moving-frame UPDE solver engine plus its run entry points and state; the engine's methods share state. |
upde/pha_c_formal_obligation |
~1110 | One PHA-C kinematic proof obligation — its construction, verification, and serialisation with the kinematic sub-checks. |
supervisor/federated_transport |
~1100 | One signed-transport lifecycle (build → validate → replay) over a shared hashing/normalisation core, with a deployment-preflight feature; the envelope, replay ledger, and ~20 helpers are densely shared. |
monitor/twin_confidence |
~1080 | One twin-confidence pipeline: divergence → calibration → scoring → summary → Prometheus export, tightly coupled. |
runtime/cli/plugins/scheduler_control |
~1050 | One CLI command group — the scheduler-control sub-commands over shared payload loaders. |
nn/functional |
~1015 | The flat nn.functional namespace of integrator and metric primitives (Kuramoto / Winfree / simplicial / Stuart–Landau steps, order parameter, PLV, …), kept flat by the conventional functional-namespace contract. |
reactor_semantics/contracts |
≥1200 | One stable U0 public contract namespace. Its context, observable, semantic, relation, and regime records share validation and serialization vocabulary; relocating public classes would change documented module and pickle identities. The AST review found a 15-symbol core component plus the two mutually coupled regime-record symbols, not independent runtime subsystems. |
reactor_semantics/mif_merge_compression |
≥1200 | One strict source-bytes-to-canonical-handoff pipeline. The AST review found all 35 top-level symbols in one connected component with 66 edges across decoding, validation, projection, and serialization. |
Modules in the 900–1000 band (supervisor/evolutionary_petri_grammar,
supervisor/causal, upde/pha_c_acceptance, autotune/reward) were assessed
on the same basis and are likewise single-responsibility.
A line-count objection to any of these is answered here: the criterion is the
call-graph, the threshold guard (--check) prevents regressions, and the
remaining large modules are cohesive by deliberate review. If a future change
makes one genuinely multi-responsibility, split it; if it crosses the split
threshold and is still one responsibility, record it in the allowlist with a
reason.