Cabal transitive constraint puzzle
Dear Cafe and GHC-devs, I am happy to see that Yamamoto-san (cc:d) (the maintainer of the `tls` package) is extending it to include support for MLKEM key agreement. This in turn depends on the `mlkem` library by Olivier Chéron (also many thanks!). Now it turns out that for maximum portability, `mlkem` in turn has a build flag `use_crypton` that selects either `crypton`, or (current default) the older `cryptonite` library as its dependency. So the dependen graph is: tls <- mlkem <- if (use_crypton): crypton else: cryptonite But, at this, point `tls` is crypton-only, and no longer builds with the older cryptonite equivalents. Therefore `tls` adds constraints: mlkem +use_crypton to its `cabal.project` file, which solves the problem for `tls`, but sadly not for any projects that in turn depend on `tls`. To them the constraints in the `cabal.project` file of `tls` are effectively invisible. Since the default flag value in `mlkem` is for now `use_crypton: false`, each project that wants to use the (work-in-progress) `tls` appears to need to explictly know that `tls` requires a non-default build of `mlkem`, and must add the same constraints to its `cabal.project`. That transitive explicit configuration requirement is not something one can reasonably ask projects that depend on `tls to have to add. And of course, more generally, any other such indirect flag-dependent conflict must not become a burden on all consumers of library B that is only compatible with a specific variant of library C. How is this situation supposed to be handled? Is there something in cabal that can automatically convince the solver to select a setting of the `mlkem` flag that is compatible with `tls`? Must `mlkem` be forked instead to be two libraries, one dependent on `cryptonite` and another dependent on `crypton`? I've been unable to find a satisfactory workaround. Any help appreciated. -- Viktor.
On Thu, 5 Mar 2026, Viktor Dukhovni wrote:
I am happy to see that Yamamoto-san (cc:d) (the maintainer of the `tls` package) is extending it to include support for MLKEM key agreement. This in turn depends on the `mlkem` library by Olivier Chéron (also many thanks!).
Now it turns out that for maximum portability, `mlkem` in turn has a build flag `use_crypton` that selects either `crypton`, or (current default) the older `cryptonite` library as its dependency.
The use_crypton flag is declared to be manual. I think this is the problem, it should be automatic.
On 5 Mar 2026, at 6:21 pm, Henning Thielemann
wrote: The use_crypton flag is declared to be manual. I think this is the problem, it should be automatic.
Yes, that's one of the potential obstacles, but making automatic does not help. The solver does not see the conflict, even if `tsl` declares `conflicts: cryptonite`, and the flag is automatic. I am sure we could convince Olivier to make it automatic, if that would help. -- Viktor.
On Thu, 5 Mar 2026, Viktor Dukhovni wrote:
On 5 Mar 2026, at 6:21 pm, Henning Thielemann
wrote: The use_crypton flag is declared to be manual. I think this is the problem, it should be automatic.
Yes, that's one of the potential obstacles, but making automatic does not help. The solver does not see the conflict, even if `tsl` declares `conflicts: cryptonite`, and the flag is automatic.
What is `conflicts: cryptonite`? A Cabal package description field? I do not see it in Cabal-3.16 docs.
On Thu, Mar 05, 2026 at 08:44:25AM +0100, Henning Thielemann wrote:
Yes, that's one of the potential obstacles, but making automatic does not help. The solver does not see the conflict, even if `tsl` declares `conflicts: cryptonite`, and the flag is automatic.
What is `conflicts: cryptonite`? A Cabal package description field? I do not see it in Cabal-3.16 docs.
AI hallucination apparently. I was on a plane with spotty Wifi, and tried to make to with bad advice from Gemini. So it seems there isn't any practical way to express that `tls` **requires** the `use_crypton` flag in `mlkem` not only in its own build, but also when built as a dependency of other packages. :-( What is the correct way out of this bind? -- Viktor. 🇺🇦 Слава Україні!
On Thu, 5 Mar 2026, Viktor Dukhovni wrote:
On Thu, Mar 05, 2026 at 08:44:25AM +0100, Henning Thielemann wrote:
Yes, that's one of the potential obstacles, but making automatic does not help. The solver does not see the conflict, even if `tsl` declares `conflicts: cryptonite`, and the flag is automatic.
What is `conflicts: cryptonite`? A Cabal package description field? I do not see it in Cabal-3.16 docs.
AI hallucination apparently. I was on a plane with spotty Wifi, and tried to make to with bad advice from Gemini. So it seems there isn't any practical way to express that `tls` **requires** the `use_crypton` flag in `mlkem` not only in its own build, but also when built as a dependency of other packages. :-(
Ok, I see why the automatic flag does not solve the problem: In terms of package dependencies there is no conflict for Cabal between `crypton` and `cryptonite` and thus it chooses both of them. So, Cabal would actually need something like a "conflicts" field in the package description.
On Thu, Mar 05, 2026 at 11:31:17AM +0100, Henning Thielemann via ghc-devs wrote:
AI hallucination apparently. I was on a plane with spotty Wifi, and tried to make to with bad advice from Gemini. So it seems there isn't any practical way to express that `tls` **requires** the `use_crypton` flag in `mlkem` not only in its own build, but also when built as a dependency of other packages. :-(
Ok, I see why the automatic flag does not solve the problem: In terms of package dependencies there is no conflict for Cabal between `crypton` and `cryptonite` and thus it chooses both of them. So, Cabal would actually need something like a "conflicts" field in the package description.
Yes, the Gemini AI was hallucinating its existence, but perhaps that's not entirely a bad idea for a new feature. -- Viktor. 🇺🇦 Слава Україні!
https://github.com/haskell/cabal/issues/11053 On 3/6/26 04:12, Viktor Dukhovni via ghc-devs wrote:
On Thu, Mar 05, 2026 at 11:31:17AM +0100, Henning Thielemann via ghc-devs wrote:
AI hallucination apparently. I was on a plane with spotty Wifi, and tried to make to with bad advice from Gemini. So it seems there isn't any practical way to express that `tls` **requires** the `use_crypton` flag in `mlkem` not only in its own build, but also when built as a dependency of other packages. :-( Ok, I see why the automatic flag does not solve the problem: In terms of package dependencies there is no conflict for Cabal between `crypton` and `cryptonite` and thus it chooses both of them. So, Cabal would actually need something like a "conflicts" field in the package description. Yes, the Gemini AI was hallucinating its existence, but perhaps that's not entirely a bad idea for a new feature.
A good rule of thumb for automatic flag selection is to make "constraints" induced on dependency solver disjoint, which would make flag values a function of install plan. E.g. if flag(old-base) build-depends: base <4 else build-depends: base >=4 Given an install plan, I can tell what value the `old-base` would been assigned. That is deterministic function. I don't see how you would translate `conflicts` to package dependency problem constraint. Importantly: having `crypton` and `cryptonite` in different parts of install plan is completely fine. No individual package (author) should be able dictate that you cannot use some other package in the same project. So, the fake-depends https://github.com/haskell/cabal/issues/11053 I linked **adds dependency to be satisfied** (just not exposed), while unrestrained `constraints` proposed in that issue later would be too powerful. - Oleg On 3/6/26 04:12, Viktor Dukhovni via ghc-devs wrote:
On Thu, Mar 05, 2026 at 11:31:17AM +0100, Henning Thielemann via ghc-devs wrote:
AI hallucination apparently. I was on a plane with spotty Wifi, and tried to make to with bad advice from Gemini. So it seems there isn't any practical way to express that `tls` **requires** the `use_crypton` flag in `mlkem` not only in its own build, but also when built as a dependency of other packages. :-( Ok, I see why the automatic flag does not solve the problem: In terms of package dependencies there is no conflict for Cabal between `crypton` and `cryptonite` and thus it chooses both of them. So, Cabal would actually need something like a "conflicts" field in the package description. Yes, the Gemini AI was hallucinating its existence, but perhaps that's not entirely a bad idea for a new feature.
Hi Viktor, On 05/03/2026 05:54, Viktor Dukhovni wrote:
Now it turns out that for maximum portability, `mlkem` in turn has a build flag `use_crypton` that selects either `crypton`, or (current default) the older `cryptonite` library as its dependency. So the dependen graph is:
tls <- mlkem <- if (use_crypton): crypton else: cryptonite
The general principle (albeit one that is not necessarily widely understood or well advertised) is that package APIs should not depend on flag assignments, for precisely the reason you outline:
Since the default flag value in `mlkem` is for now `use_crypton: false`, each project that wants to use the (work-in-progress) `tls` appears to need to explictly know that `tls` requires a non-default build of `mlkem`, and must add the same constraints to its `cabal.project`.
That transitive explicit configuration requirement is not something one can reasonably ask projects that depend on `tls to have to add.
See https://github.com/haskell/cabal/issues/8128 for more discussion of this. So I think the preferable solution is to resolve this at the level of `mlkem`, either by forking it into two libraries in order to continue supporting both dependencies, or by making a major version bump that switches the dependency to `crypton`. The latter seems the most reasonable choice given that `cryptonite` is now unmaintained and `crypton` is increasingly being adopted across the ecosystem as a replacement. Pragmatically it may be helpful to flip the default value of the flag but keep it around, so any users inconvenienced by this can set the flag manually. Hope this helps, Adam -- Adam Gundry, Haskell Consultant Well-Typed LLP, https://www.well-typed.com/ Registered in England & Wales, OC335890 27 Old Gloucester Street, London WC1N 3AX, England
On Thu, 5 Mar 2026, Adam Gundry wrote:
On 05/03/2026 05:54, Viktor Dukhovni wrote:
Now it turns out that for maximum portability, `mlkem` in turn has a build flag `use_crypton` that selects either `crypton`, or (current default) the older `cryptonite` library as its dependency. So the dependen graph is:
tls <- mlkem <- if (use_crypton): crypton else: cryptonite
The general principle (albeit one that is not necessarily widely understood or well advertised) is that package APIs should not depend on flag assignments, for precisely the reason you outline:
I think that condition is satisfied here: The API of tls is independent from whether mlkem calls crypton or cryponite. So in principle things should work if `tls` uses `crypton`, and `mlkem` uses `cryptonite`, as Cabal might choose as default.
On Thu, Mar 05, 2026 at 12:48:58PM +0100, Henning Thielemann wrote:
The general principle (albeit one that is not necessarily widely understood or well advertised) is that package APIs should not depend on flag assignments, for precisely the reason you outline:
I think that condition is satisfied here: The API of tls is independent from whether mlkem calls crypton or cryponite.
So in principle things should work if `tls` uses `crypton`, and `mlkem` uses `cryptonite`, as Cabal might choose as default.
Sadly, things don't turn out that way. Various functions in the MLKEM API carry type class constraints like `MonadRandom r`, ... where the MonadRandom in question is either the one from `cryptonite` or `crypton`, and so not the same API as far as the type system is concerned. In paricular, when `tls` (using `crypton`) tries to provide a `MonadRandom r`, it is the *wrong* one when `mlkem` is built with `cryptonite` and the code fails to compile. Bottom line, the two variants of `mlkem` don't end up offering the same API (distinct type signatures, that only look the same). So either forking `mlkem` or a major version bump seem to be the sensible choices. I hope Olivier can be convinced to go the version bump route. -- Viktor. 🇺🇦 Слава Україні!
So either forking `mlkem` or a major version bump seem to be the sensible choices. I hope Olivier can be convinced to go the version bump route.
I can access codeberg.org today: - https://codeberg.org/ocheron/hs-mlkem/issues/4 - https://codeberg.org/ocheron/hs-mlkem/issues/5 I should resolve an issue of "crypton" from Oliver in #4. Also, we are trying to remove "basement" and "memory" from our libraries including "mlkem" (in #5). For more information, please read: - https://github.com/kazu-yamamoto/crypton/pull/67 --Kazu
participants (5)
-
Adam Gundry -
Henning Thielemann -
Kazu Yamamoto -
Oleg Grenrus -
Viktor Dukhovni