You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
drift(basis): the import-triad term penalizes decomposition quadratically, and layered_dag's ideal (021U = 0) is unreachable for any package whose modules share a dependency #554
Two independent measurements on Ownima owner-api, from two sessions that did not coordinate on this, say the same thing: splitting a module raises a domain's drift score, and the term that does it has an ideal no real package can reach. Distinct from #552 (which is about what gets scored); this is about the basis and the ideal vectors.
The observation
A colleague refactored app/domains/reservation/routes.py — four helper functions moved verbatim into two new modules in the same package, one function deleted, no behaviour changed, no raise altered. Drift on app.domains.reservation went 0.494 → 0.506. They report t_calls unchanged and the whole delta in t_imports.
My own run on a slightly earlier main put the same domain at 0.468 with its single largest violation being:
T_imports[021U] = 0.489 vs ideal 0.000 (+0.24)
layered_dag's declared t_imports ideal is [0.5, 0.0, 0.5, 0, …] — 021D and 021C only, zero 021U.
The mechanism, measured rather than reasoned
021U is the convergent triad: two sources pointing at one target. So every pair of modules in a package that import the same thing contributes one. In app.domains.reservation, production code only (is_test = 0):
WITH res AS (SELECT id FROM nodes WHERE id LIKE'app.domains.reservation%'AND is_test=0),
imp AS (SELECT DISTINCTe.source, e.targetFROM edges e JOIN res s ONe.source=s.idWHEREe.typeLIKE'%IMPORT%')
SELECTCOUNT(*), SUM(n), SUM(n*(n-1)/2)
FROM (SELECT target, COUNT(DISTINCT source) n FROM imp GROUP BY target HAVING n>=2);
-- shared_targets 108 | converging_edges 358 | 021U triads 687
The top shared targets:
target
importers
app.gen.entities
14
app.gen.entities.Reservation
11
app.core.constants
11
datetime
9
app.storage.base
9
app.models
9
687 convergent triads out of ~20 production modules, and the single biggest contributor is the domain importing its own generated entity types. datetime is in the list.
Why a split makes it strictly worse: convergence on one target grows as n(n-1)/2 in the number of importing modules. Take one module that imports app.gen.entities, split it into three that each still need it, and that target's contribution goes from C(14,2) = 91 to C(16,2) = 120. The move adds no edge the code did not already have — it re-partitions the same imports across more files, and the census counts pairs of files.
Why this is a defect and not a tuning question
The ideal is unreachable.021U = 0 asks that no two modules in a package share any dependency — not the domain's entity types, not its constants, not datetime. No package satisfies that above two modules.
It inverts the signal. Decomposition is the refactor an architectural metric should reward; here it is the one thing guaranteed to make the number worse. A domain can only improve t_imports by merging modules.
It is already costing a decision. Rather than loosen or tighten a tolerance to accommodate an artifact, my colleague closed their refactor's drift criterion as unreachable by this route. That is the right call and it means the metric gave them nothing — which is the outcome worth avoiding.
Normalize the import census per module, not per triad pair, so re-partitioning the same dependency set across more files is neutral by construction. This is the direct fix for the quadratic.
Reconsider whether layered_dag should specify 021U = 0 at all. This is the substantive question. A layered DAG in practice converges: many modules in a layer depend on the layer below. If the template means "no convergence", it is describing a tree, not a DAG — and a tree is not what any of these domains is or should be.
Weight t_calls above t_imports. Calls carry behaviour; imports are substantially a function of file granularity, which is a style choice. Note the colleague's refactor left t_calls untouched — the term that tracked the behaviour correctly reported that nothing happened.
(1) and (2) compose and are mechanical. (3) wants a decision from whoever owns the alphabet. (4) is a knob that would mask (1)–(3) rather than fix them.
Reproduction
owner-api, ingest root ownima-backend, ontology ownima-backend/docs/ontology/patterns.yaml, domain app.domains.reservation, expected pattern layered_dag. Compare drift_score and actual.t_imports across a commit that only moves functions between modules of one package.
Two independent measurements on Ownima owner-api, from two sessions that did not coordinate on this, say the same thing: splitting a module raises a domain's drift score, and the term that does it has an ideal no real package can reach. Distinct from #552 (which is about what gets scored); this is about the basis and the ideal vectors.
The observation
A colleague refactored
app/domains/reservation/routes.py— four helper functions moved verbatim into two new modules in the same package, one function deleted, no behaviour changed, no raise altered. Drift onapp.domains.reservationwent 0.494 → 0.506. They reportt_callsunchanged and the whole delta int_imports.My own run on a slightly earlier main put the same domain at 0.468 with its single largest violation being:
layered_dag's declaredt_importsideal is[0.5, 0.0, 0.5, 0, …]— 021D and 021C only, zero 021U.The mechanism, measured rather than reasoned
021U is the convergent triad: two sources pointing at one target. So every pair of modules in a package that import the same thing contributes one. In
app.domains.reservation, production code only (is_test = 0):The top shared targets:
app.gen.entitiesapp.gen.entities.Reservationapp.core.constantsdatetimeapp.storage.baseapp.models687 convergent triads out of ~20 production modules, and the single biggest contributor is the domain importing its own generated entity types.
datetimeis in the list.Why a split makes it strictly worse: convergence on one target grows as
n(n-1)/2in the number of importing modules. Take one module that importsapp.gen.entities, split it into three that each still need it, and that target's contribution goes fromC(14,2) = 91toC(16,2) = 120. The move adds no edge the code did not already have — it re-partitions the same imports across more files, and the census counts pairs of files.Why this is a defect and not a tuning question
021U = 0asks that no two modules in a package share any dependency — not the domain's entity types, not its constants, notdatetime. No package satisfies that above two modules.t_importsby merging modules.Candidate directions, least invasive first
datetimeconvergence says nothing about design, andapp.gen.*is protoc output.nodes.is_generatedand thenamespacecolumn are already there (see feat(drift): cgis_drift has no --exclude/--scope, so the hygiene gate measures test volume and logging density #552, whereis_testturned out to be populated and unread).layered_dagshould specify021U = 0at all. This is the substantive question. A layered DAG in practice converges: many modules in a layer depend on the layer below. If the template means "no convergence", it is describing a tree, not a DAG — and a tree is not what any of these domains is or should be.t_callsabovet_imports. Calls carry behaviour; imports are substantially a function of file granularity, which is a style choice. Note the colleague's refactor leftt_callsuntouched — the term that tracked the behaviour correctly reported that nothing happened.(1) and (2) compose and are mechanical. (3) wants a decision from whoever owns the alphabet. (4) is a knob that would mask (1)–(3) rather than fix them.
Reproduction
owner-api, ingest root
ownima-backend, ontologyownima-backend/docs/ontology/patterns.yaml, domainapp.domains.reservation, expected patternlayered_dag. Comparedrift_scoreandactual.t_importsacross a commit that only moves functions between modules of one package.