May 21, 5-6 AM (27)
May 21, 6-7 AM (14)
May 21, 7-8 AM (22)
May 21, 8-9 AM (34)
May 21, 9-10 AM (45)
May 21, 10-11 AM (37)
May 21, 11-12 PM (27)
May 21, 12-1 PM (63)
May 21, 1-2 PM (68)
May 21, 2-3 PM (60)
May 21, 3-4 PM (53)
May 21, 4-5 PM (20)
May 21, 5-6 PM (27)
May 21, 6-7 PM (27)
May 21, 7-8 PM (25)
May 21, 8-9 PM (23)
May 21, 9-10 PM (3)
May 21, 10-11 PM (29)
May 21, 11-12 AM (10)
May 22, 12-1 AM (16)
May 22, 1-2 AM (6)
May 22, 2-3 AM (8)
May 22, 3-4 AM (4)
May 22, 4-5 AM (11)
May 22, 5-6 AM (10)
May 22, 6-7 AM (21)
May 22, 7-8 AM (13)
May 22, 8-9 AM (38)
May 22, 9-10 AM (12)
May 22, 10-11 AM (18)
May 22, 11-12 PM (25)
May 22, 12-1 PM (24)
May 22, 1-2 PM (34)
May 22, 2-3 PM (56)
May 22, 3-4 PM (13)
May 22, 4-5 PM (29)
May 22, 5-6 PM (13)
May 22, 6-7 PM (19)
May 22, 7-8 PM (20)
May 22, 8-9 PM (12)
May 22, 9-10 PM (12)
May 22, 10-11 PM (41)
May 22, 11-12 AM (12)
May 23, 12-1 AM (9)
May 23, 1-2 AM (0)
May 23, 2-3 AM (3)
May 23, 3-4 AM (1)
May 23, 4-5 AM (1)
May 23, 5-6 AM (4)
May 23, 6-7 AM (12)
May 23, 7-8 AM (1)
May 23, 8-9 AM (3)
May 23, 9-10 AM (1)
May 23, 10-11 AM (1)
May 23, 11-12 PM (5)
May 23, 12-1 PM (1)
May 23, 1-2 PM (6)
May 23, 2-3 PM (5)
May 23, 3-4 PM (5)
May 23, 4-5 PM (4)
May 23, 5-6 PM (0)
May 23, 6-7 PM (3)
May 23, 7-8 PM (23)
May 23, 8-9 PM (1)
May 23, 9-10 PM (9)
May 23, 10-11 PM (21)
May 23, 11-12 AM (27)
May 24, 12-1 AM (9)
May 24, 1-2 AM (0)
May 24, 2-3 AM (1)
May 24, 3-4 AM (1)
May 24, 4-5 AM (0)
May 24, 5-6 AM (3)
May 24, 6-7 AM (1)
May 24, 7-8 AM (2)
May 24, 8-9 AM (2)
May 24, 9-10 AM (4)
May 24, 10-11 AM (4)
May 24, 11-12 PM (1)
May 24, 12-1 PM (7)
May 24, 1-2 PM (46)
May 24, 2-3 PM (5)
May 24, 3-4 PM (3)
May 24, 4-5 PM (18)
May 24, 5-6 PM (2)
May 24, 6-7 PM (4)
May 24, 7-8 PM (13)
May 24, 8-9 PM (10)
May 24, 9-10 PM (15)
May 24, 10-11 PM (34)
May 24, 11-12 AM (42)
May 25, 12-1 AM (9)
May 25, 1-2 AM (5)
May 25, 2-3 AM (6)
May 25, 3-4 AM (1)
May 25, 4-5 AM (6)
May 25, 5-6 AM (14)
May 25, 6-7 AM (17)
May 25, 7-8 AM (17)
May 25, 8-9 AM (32)
May 25, 9-10 AM (43)
May 25, 10-11 AM (64)
May 25, 11-12 PM (33)
May 25, 12-1 PM (43)
May 25, 1-2 PM (40)
May 25, 2-3 PM (20)
May 25, 3-4 PM (27)
May 25, 4-5 PM (16)
May 25, 5-6 PM (6)
May 25, 6-7 PM (7)
May 25, 7-8 PM (11)
May 25, 8-9 PM (12)
May 25, 9-10 PM (16)
May 25, 10-11 PM (44)
May 25, 11-12 AM (26)
May 26, 12-1 AM (12)
May 26, 1-2 AM (11)
May 26, 2-3 AM (8)
May 26, 3-4 AM (11)
May 26, 4-5 AM (6)
May 26, 5-6 AM (9)
May 26, 6-7 AM (26)
May 26, 7-8 AM (43)
May 26, 8-9 AM (39)
May 26, 9-10 AM (42)
May 26, 10-11 AM (45)
May 26, 11-12 PM (59)
May 26, 12-1 PM (34)
May 26, 1-2 PM (50)
May 26, 2-3 PM (50)
May 26, 3-4 PM (18)
May 26, 4-5 PM (20)
May 26, 5-6 PM (13)
May 26, 6-7 PM (20)
May 26, 7-8 PM (12)
May 26, 8-9 PM (15)
May 26, 9-10 PM (15)
May 26, 10-11 PM (35)
May 26, 11-12 AM (30)
May 27, 12-1 AM (16)
May 27, 1-2 AM (8)
May 27, 2-3 AM (9)
May 27, 3-4 AM (5)
May 27, 4-5 AM (32)
May 27, 5-6 AM (9)
May 27, 6-7 AM (49)
May 27, 7-8 AM (63)
May 27, 8-9 AM (37)
May 27, 9-10 AM (74)
May 27, 10-11 AM (83)
May 27, 11-12 PM (30)
May 27, 12-1 PM (50)
May 27, 1-2 PM (38)
May 27, 2-3 PM (53)
May 27, 3-4 PM (37)
May 27, 4-5 PM (6)
May 27, 5-6 PM (17)
May 27, 6-7 PM (18)
May 27, 7-8 PM (24)
May 27, 8-9 PM (13)
May 27, 9-10 PM (13)
May 27, 10-11 PM (29)
May 27, 11-12 AM (27)
May 28, 12-1 AM (9)
May 28, 1-2 AM (3)
May 28, 2-3 AM (4)
May 28, 3-4 AM (2)
May 28, 4-5 AM (8)
May 28, 5-6 AM (6)
3,337 commits this week May 21, 2026 - May 28, 2026
cardano-tracer, grafana-datasource: extend series labels, rename node-state endpoint, misc
- Acceptors/Utils.hs: include node_name alongside node_id in SeriesIdentifier
- Component.hs: generalise insert to accept SeriesIdentifier directly; fix
  pruning period default (was 3600 ms ≈ 3.6 s, now 15 min)
- Component/Trace.hs: update TimeseriesTraceInsert to carry SeriesIdentifier;
  emit query_text/query_result only at DDetailed; lower Prune severity to Debug
- TimeseriesServer.hs: rename node/state → node/sync-progress; remove #if RTVIEW
  guard (endpoint is now unconditionally available)
- Utils.hs: move NodeStateWrapper (and its FromJSON instance) here from
  RTView/Update/NodeState.hs so it can be shared without the RTVIEW flag;
  move vector from rtview-only to unconditional build-depends
- RTView/Update/NodeState.hs: remove NodeStateWrapper definition (now in Utils)
- grafana-datasource: rename node-state → node-sync-progress throughout
  (QueryEditor, datasource, types, dashboard); rename dashboard to rtview-port
- grafana-datasource: add README with build and run instructions
Add Certs PoV: per-step ≡ᵐᵗ + coin bridges, RTC lift
New modules under src/Ledger/Dijkstra/Specification/Certs/Properties/:
+  PoVLemmas.lagda.md  (CERT-level)
+  PoV.lagda.md        (CERTS-level)

PoVLemmas exports:
+  CERT-pov: preservation of value at one CERT step
+  CERT-pots-≡ᵐᵗ: per-step ≡ᵐ-componentwise triple bridge
+  CERT-coinFromDeposits-step: per-step coin bridge (derived)
+  Triple machinery: pots, coinFromDeposits-pots,
   updateCertDeposit-list, pots-updateCertDeposits
+  PoolDepositsAligned, Is-just-isPoolRegistered⇒∈-dom

PoV exports a bundled Certs-PoV module parameterised by
indexedSumᵛ'-∪ and PoolDepositsAligned-CERT, providing:
+  CERTS-pov: preservation across the closure
+  CERTS-Deposits-Bridge.CERTS-coinFromDeposits-updateCertDeposits:
   the closed-form coin bridge consumed by LEDGER-pov

The triple-form per-step bridge from the previous Ledger-PoV branch
required a deferred propositional equation m ∪ˡ ❴ k , v ❵ ≡ m
(when k ∈ dom m). This PR drops that parameter, using instead the
upstream ≡ᵐ-componentwise singleton-∈-∪ˡ plumbed through the closed
form via ∪⁺-cong-r, ∪ˡ-cong, restrict-cong, and collapsed to a coin
equality via ≡ᵉ-getCoin.

Refs #1185
Add Certs PoV: per-step ≡ᵐᵗ + coin bridges, RTC lift
New modules under src/Ledger/Dijkstra/Specification/Certs/Properties/:
+  PoVLemmas.lagda.md  (CERT-level)
+  PoV.lagda.md        (CERTS-level)

PoVLemmas exports:
+  CERT-pov: preservation of value at one CERT step
+  CERT-pots-≡ᵐᵗ: per-step ≡ᵐ-componentwise triple bridge
+  CERT-coinFromDeposits-step: per-step coin bridge (derived)
+  Triple machinery: pots, coinFromDeposits-pots,
   updateCertDeposit-list, pots-updateCertDeposits
+  PoolDepositsAligned, Is-just-isPoolRegistered⇒∈-dom

PoV exports a bundled Certs-PoV module parameterised by
indexedSumᵛ'-∪ and PoolDepositsAligned-CERT, providing:
+  CERTS-pov: preservation across the closure
+  CERTS-Deposits-Bridge.CERTS-coinFromDeposits-updateCertDeposits:
   the closed-form coin bridge consumed by LEDGER-pov

The triple-form per-step bridge from the previous Ledger-PoV branch
required a deferred propositional equation m ∪ˡ ❴ k , v ❵ ≡ m
(when k ∈ dom m). This PR drops that parameter, using instead the
upstream ≡ᵐ-componentwise singleton-∈-∪ˡ plumbed through the closed
form via ∪⁺-cong-r, ∪ˡ-cong, restrict-cong, and collapsed to a coin
equality via ≡ᵉ-getCoin.

Refs #1185
add ghcXX-minimal-ghc-web flavor: wasm + JS backend dev tooling
The wasm and JavaScript backends need a small set of dev-time tools
that aren't part of a stock Haskell shell:

  * nodejs_22   — required for utils/jsffi/post-link.mjs (uses
                  import.meta.filename added in Node 20.11; Ubuntu's
                  apt nodejs is 18.x and silently breaks the post-link
                  step) and for JSFFI host execution at test time
  * wabt        — wasm-objdump for inspecting custom sections (e.g.
                  detecting ghc_wasm_jsffi imports) in wasm modules
  * wasmtime    — pure-WASI runtime, when a wasm module has no JSFFI
                  imports and can run standalone
  * emscripten  — the JavaScript backend's C toolchain (emcc / em++ /
                  emar / emnm / emranlib / emstrip)

Currently downstream consumers (e.g. stable-haskell/ghc's wasm
cross-compiler CI) bootstrap these per-platform via apt + NodeSource +
nix-env + curl installers, plus PATH workarounds for devx scrubbing
/usr/bin. Shipping them in the flavor collapses ~70 lines of
platform-shell to one `shell:` line in user workflows.

Deliberately NOT bundled: wasi-sdk. Its version needs to match the
wasm32-wasi-ghc cross-compiler bundle that ghc-wasm-meta
(https://gitlab.haskell.org/ghc/ghc-wasm-meta) owns, so keeping that
pin in ghc-wasm-meta avoids version drift across two trees. Users
still bootstrap wasi-sdk via ghc-wasm-meta for wasm32-wasi builds.

Closure cost on aarch64-darwin (paired with the prior trim commit):

  ghc98-minimal-ghc      (current master) :  6.12 GB
  ghc98-minimal-ghc      (after trim PR)  :  4.00 GB  (-2.12 GB)
  ghc98-minimal-ghc-web  (this commit)    :  5.99 GB  (+1.99 GB)

Net: the web flavor with the full toolchain ends up SMALLER than the
current untrimmed minimal-ghc, because the trim commit removed
ghc-9.10.3 and emscripten's LLVM/apple-sdk now dedupes against the
shell's base nixpkgs pin (no version fragmentation).

Comfortably under the 10 GB GitHub Actions per-repo cache cap on
both Darwin and Linux (Linux delta is similar magnitude — emscripten
+ closure-compiler are the heavy hitters on both).

Verified inside the patched shell:

  $ ghc --version            # 9.8.4
  $ cabal --version          # 3.17.0.0
  $ happy --version          # 2.1.7
  $ alex --version           # 3.5.4.0
  $ git --version            # 2.51.2 (gitMinimal)
  $ node --version           # v22.21.1
  $ wasm-objdump --version   # 1.0.37
  $ wasmtime --version       # 38.0.3
  $ emcc --version           # 4.0.12-git

All on the expected store paths.
chore(deps): bump tmp in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [tmp](https://github.com/raszi/node-tmp).


Updates `tmp` from 0.2.5 to 0.2.7
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.5...v0.2.7)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.7
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <[email protected]>
trim -minimal-ghc closure: build happy/alex with the shell's GHC
The `withGHCTooling` block in dynamic.nix sourced `happy` and `alex`
from nixpkgs's `pkgs.haskellPackages`, which builds them with a
different GHC than the shell's `compiler` (currently ghc-9.10.3 in
nixpkgs vs ghc-9.8.4 in our shell). The Haskell library outputs of
both packages live under `lib/ghc-<other-ver>/lib/*.dylib`; their
.dylib path strings anchor the foreign GHC in the closure via Nix's
reference scanner.

On aarch64-darwin this dragged in ghc-9.10.3 (1.40 GB) and
ghc-9.10.3-doc (753 MB) — ~2.15 GB of essentially-unused payload.

Switch to haskell.nix's `tool` builder (same pattern as cross-js.nix
and cross-windows.nix), which builds happy/alex with the shell's
`compiler-nix-name`. The resulting library outputs reference the GHC
that's already in the closure rather than dragging in a second one.

While here:
* swap `git` → `gitMinimal` — drops the heavyweight perl-modules and
  git-doc that aren't useful inside the dev shell (~150 MB cascade).
* tool-map.nix: drop `inherit cabalProjectLocal` from happy/alex.
  They're standard mainline packages and build cleanly from regular
  hackage; the inherited cabalProjectLocal pinned a head.hackage SHA
  that was stale and broke fresh evaluations of `(tool "happy")` /
  `(tool "alex")`. While there, bump happy 1.20.1.1 → 2.1.7 and
  alex 3.2.7.3 → 3.5.4.0 to match what nixpkgs.haskellPackages was
  shipping previously, so users see no behavioural change.

Measured on aarch64-darwin (ghc98-minimal-ghc):
  before:  6.12 GB / 228 paths
  after :  4.00 GB / 196 paths
  saved :  2.12 GB (34.7% smaller)

Top removed paths:
  ghc-9.10.3              1399 MB
  ghc-9.10.3-doc           753 MB
  git-2.51.2                49 MB
  git-2.51.2-doc            15 MB
  perl5.40.0-SSLeay/Mozilla/IO-Socket-SSL (gitMinimal cascade)

Verified inside the patched shell:
  $ ghc --version    # 9.8.4
  $ cabal --version  # 3.17.0.0
  $ happy --version  # 2.1.7 (same as before)
  $ alex --version   # 3.5.4.0 (same as before)
  $ git --version    # 2.51.2
Add cabal v2-build-based component builder ("v2 builder") (#2513)
* v2 builder: cabal v2-build slicing, dev shell, plan-nix matching

Adds the v2 component builder alongside v1 and a project-level
`builderVersion` switch that picks between them.  Where v1 runs
`Setup.hs configure / build` per component, v2 runs `cabal
v2-build` against a per-slice slicing repo and pre-composed
starting store, registers each new unit under
`$out/store/ghc-<ver>[-inplace]/...`, and emits a plan-entry
diff against plan-nix when something diverges.

Highlights:

* `builder/comp-v2-builder.nix` — per-component slice builder.
  Drives a per-slice cabal.project (constraints pinned from
  plan-nix's `depends`, `extra-packages:` listing every
  transitive dep, per-pkg `flags:` / `ghc-options:` /
  `configure-options:` blocks) and a per-slice slicing repo
  of source tarballs and a `00-index.tar.gz` so cabal's solver
  hits a closed package set.  Hands the cabal.project plus
  the slice's direct dep slices off to `build-cabal-slice.nix`
  to actually run cabal.
* `builder/build-cabal-slice.nix` — generic runner the per-
  component builder calls into.  Composes the starting store
  via `lndir` from every dep slice (walks `pkgsHostTarget`
  for direct deps and follows each entry's
  `$out/nix-support/transitive-deps` for the rest of the
  closure, so transitive dep slices stay out of nix-side
  `buildInputs`), runs `cabal v2-build`, captures every newly
  installed unit back into `$out/store`, writes the slice's
  own `nix-support/transitive-deps` for downstream consumers,
  and on `dryRunOnly` emits a per-entry diff between cabal's
  planned `plan.json` and plan-nix's expectations.
* `builder/compose-store.nix` — `lndir`-based store
  composition with the same direct + transitive-deps walk.
  Used by `comp-v2-builder` for the `passthru.store` exposed
  on each slice and by `shell-for-v2.nix` for the dev shell's
  composed dep store.
* `builder/cabal-install-patches/{prune-unreachable-sublibs*,
  skip-installed-revdeps-in-completed}.patch` — three
  cabal-install patches the v2 slice solver needs to keep its
  plan in sync with plan-nix.
* `builder/shell-for-v2.nix` — v2 dev shell.  Resolves each
  selected component's direct external `component.depends` to
  its library (via `haskellLib.dependToLib`, so sublib
  references resolve to their sublib slices), composes those
  into a starting cabal store via `composeStore` (the
  transitive closure is materialised at build time from each
  slice's `nix-support/transitive-deps`), exposes the result
  via `~/.cabal/store/<ghc>-inplace/` (the default
  `exposePackagesVia = "cabal-store"`) or via a
  `GHC_ENVIRONMENT`-wrapped ghc (`exposePackagesVia =
  "ghc-pkg"`), and merges `passthru.depSlices` from
  `inputsFrom` so cross + native shells both see each other's
  slices.
* `builder/{default,hspkg-builder}.nix` — dispatch on
  `builderVersion`: the per-component derivation at
  `hsPkgs.<pkg>.components.<kind>.<n>` is whichever builder
  the project selects (no per-component opt-in).
  `hspkg-builder.nix` also resolves `homeDependIds` and the
  `packageRefersOwnExe` flag from `config.plan-json-by-id`
  (the project-level index) so these per-component lookups
  are O(log N) attrset hits instead of linear scans over the
  install-plan.
* `modules/{component-driver,project-common,shell}.nix` —
  `builderVersion`, `crossTemplateHaskellSupport`,
  `exposePackagesVia` options.
* `modules/install-plan/configure-args.nix` — filter
  `--ghc-option=-hide-all-packages` out of `ghcOptions` for
  the v2 builder (cabal injects it on every Setup configure
  call, and round-tripping it through cabal.project would
  duplicate it in `pkgHashProgramArgs`); extract
  `--configure-option=...` entries into a new dedicated
  `package.configureOptions` option (declared in
  `modules/package-options.nix`) so the v2 builder can emit
  them back into its own cabal.project.
* `modules/install-plan/non-reinstallable.nix` — gate the
  ghcjs/wasm `ghci`-related `non-reinstallable` set on
  `builderVersion != 2`; under v2 the slice solver follows
  cabal's install plan literally.
* `lib/check.nix` — `passthru.isSlice` branch that runs the
  slice-built exe directly instead of going through v1's
  `setup test` pipeline.
* `lib/default.nix` — `uniqueWithNameKey` prefers
  `identifier.unit-id` when present so two `foo-1.2.3` slices
  with different inputs partition into different buckets.
* `lib/call-cabal-project-to-nix.nix` — set
  `CABAL_INSTALLED_PACKAGE_ID_OS = pkgs.stdenv.buildPlatform.parsed.kernel.name;`
  on the plan-to-nix derivation so the patched cabal-install
  (#2501) actually fires; without the env var the patch is a
  no-op.
* `compiler/ghc/default.nix`, `overlays/{bootstrap,tools}.nix`
  — pin GHC, `hscolour`, and the boot tools to
  `builderVersion = 1` so v2 churn doesn't trigger GHC
  rebuilds while v2 is still settling.
* `overlays/{windows,mingw_w64}.nix` — v2-builder-aware
  Windows-cross TH wrapper: `mingw_w64.nix` exposes
  `wrapGhc :: ghc -> ghc` and `iservRuntimeLibs`;
  `overlays/windows.nix` plumbs the build-platform
  `runCommand` / `makeWrapper` / `libffi` through to that
  module.
* `overlays/haskell.nix` — project-level shell dispatch
  (`shellForV1` / `shellForV2`), `crossTemplateHaskellSupport`
  plumbing, and `cabalProjectLocal` refactored so the
  Windows-host iserv-proxy linker flags can be additive.
* `test/cabal-sublib-shell/` — new test exercising a sublib
  consumer in the v2 dev shell.  Other test wiring tweaks
  (`test/{th-dlls,th-dlls-minimal,cabal-sublib}/default.nix`,
  `test/cabal.project.local`, `test/{setup-deps,
  shell-for-setup-deps}/pkg/pkg.cabal`, `test/default.nix`)
  to keep things passing under v2.
* `ci.nix` — pin `evalSystem` default to `x86_64-linux` so
  cross derivations evaluate consistently.
* `docs/dev/builder-v{1,2}.md` (+ `docs/SUMMARY.md` entry) —
  design notes for the two builders.

Performance notes:
* `transitiveTarballs` is exposed as the deduped values of
  `depTarballsDeduped`.  The un-deduped concat (which sticks
  every direct dep's full transitive list onto each consumer
  unfiltered) grows exponentially with depth in a typical
  Haskell graph (every package shares `base` / `bytestring` /
  `text` / ...) and materialises multi-GiB blocks in the
  evaluator on cardano-wallet-sized projects.
* `homeDepSliceOf` / `depTransitiveTarballsOf` fall through
  to any sublib's slice when a dep package defines only
  `library <name>` stanzas (no main library — e.g.
  `cardano-wallet-ui:{common,shelley}`).
* `propagated` keeps `libs` Windows-only (matching v1):
  cross-target slices land in the v2 dev shell's
  `inputsFrom`, and unconditionally propagating `libs` would
  drag e.g. `pkgs.liburing` from a Linux-target slice into a
  Darwin consumer's `pkgsHostTarget`.  Each slice gathers its
  own transitive sysLibs (deduped) into `extraBuildInputs`
  via `transitiveDepLibs` instead.

* v2 builder: open the cardano-wallet dev shell end-to-end

A cluster of fixes that together let the v2 dev shell evaluate, build,
and reuse prebuilt slices for cardano-wallet on aarch64-darwin.  Most
fixes are independent in spirit but interact at the slice level — they
are bundled here to keep the cardano-wallet shell green in a single
commit.

- Sublib reachability env var (`HASKELLNIX_EXTRA_SUBLIB_SEEDS`)
  threaded through both the solver-side and install-plan cabal-install
  patches.  Slices that target a sublib (or transitively depend on
  one) seed the patched reachability walk from `pkgLibDepClosure` so
  unrelated sublibs (e.g. `lib:testlib`'s deps) stay pruned.

- Source-repository-package handling in slices.  Each
  `pkg-src.type = "source-repo"` package gets a per-slice minimal git
  repo (`git init -b minimal && git add . && git commit`) wrapped
  around `${src}` and emitted as a `source-repository-package` block
  in the slice's `cabal.project` — the same shape
  `lib/call-cabal-project-to-nix.nix` produces at project level — so
  cabal hashes the same source bytes plan-nix saw and the slice's
  UnitId matches.  Build-tools (`alex`, `happy`, `hsc2hs`, ...)
  composed via `transitiveBuildToolSlices` so consumers find them in
  the cabal-store instead of rebuilding.

- Plan-nix flag honouring for source-repo packages.  Module-level
  flag overrides (e.g. cardano-wallet's `flags.release = true`) don't
  reach cabal at plan time, so plan-nix records cabal-file defaults.
  `flagBlockFor` now reads from plan-json for source-repo pkgs so the
  slice's `pkgHashFlagAssignment` matches plan-nix.

- Custom-build packages skip the UnitId check.  Plan-nix carries a
  single shared `id` per Custom-build package; cabal-install splits
  them into per-component UnitIds that can never match.  Detected via
  missing `component-name` on the plan entry.

- `propagatedBuildInputs` for `depSlices` (was `buildInputs`) so
  pkg-config deps and other propagated inputs chain transitively
  via stdenv.  Splice handles the cross-compilation case for normal
  nixpkgs deps; slice $outs themselves don't auto-swap but cardano-
  wallet's native shell doesn't trip that.

- `cp -rL` for local-package tarballs to inline out-of-bounds
  symlinks like cardano-wallet's `lib/wallet/specifications ->
  ../../specifications` that cabal-install 3.16+ rejects as
  [Cabal-7125] "Unsafe link target in tar archive".

- `$out/store` cleanup at end of installPhase.  When the expected
  UnitId is known, keep only `<uid>/`, `package.db/<uid>.conf` and
  `lib/libHS<uid>-*` — `rm -rf $ghcDir` drops everything else
  (including the lndir-composed dep symlink tree, which on a deep
  graph was 10k+ entries that fixupPhase + NAR serialisation walked
  for nothing).  Falls back to `find -type l -delete` when the UnitId
  isn't known (source-repo or `style: "local"`).  Cache files
  (`package.cache{,.lock}`) dropped from every slice — stale on
  arrival downstream and a major source of "Keeping existing link"
  spam at lndir time.

- `find -type d` instead of `ls -d */` for the unitdirs-before
  snapshot.  bash expands `*/` into args including OS-prefix
  unit-ids (`-clsss-1.8.0.1-...`), and `ls` interprets the leading
  `-` as flags.  Same root cause as the earlier `grep -qx --` fix.

- v2 shell: walk through local packages transitively, collecting
  external deps at every step.  Local packages are excluded from the
  composed cabal-store so cabal compiles them on demand from the
  user's tree; their (transitive) external deps go in.  Build-tool
  exes that resolve to local packages are similarly excluded.

- v2 shell: `pkgs.gitMinimal` on PATH so `git --version` works
  inside the shell.  On Darwin the apple-sdk overlay sets
  `DEVELOPER_DIR` to the SDK path (no tools), so `/usr/bin/git`'s
  xcrun shim fails with "tool 'git' not found"; nix-managed git
  bypasses the shim.

- Slice cabal: `--jobs=$NIX_BUILD_CORES` (capped at 4, mirrors v1)
  on the `cabal v2-build` invocation, which passes through to
  `Setup build -jN` for ghc per-module parallelism.  Build-phase
  flag, so it stays out of `pkgHashConfigureOptions` and UnitIds
  remain stable.

* v2 builder: pass `-jN` through to `Setup build` via env var

cabal-install 3.16's `setupHsBuildFlags` deliberately leaves
`buildNumJobs = mempty` (TODO: [nice to have] upstream), so neither
`cabal v2-build --jobs=N` nor `--ghc-options=-jN` actually parallelise
per-module compilation:

  * `--jobs=N` is package-level (one slice = one package) and is
    *not* threaded through to Setup build.
  * `--ghc-options=-jN` would land in `pkgHashConfigureOptions` and
    fork the slice's UnitId from plan-nix.

`setup-build-num-jobs-env.patch` adds a small read of
`HASKELLNIX_BUILD_NUM_JOBS` at the call site that constructs
BuildFlags, feeding it into `buildNumJobs`.  Build-phase flag, so
UnitIds stay stable.  build-cabal-slice.nix exports the env var
capped at 4 (mirrors v1's `-j$(($NIX_BUILD_CORES > 4 ? 4 : ...))`),
so `Setup build` invokes `ghc --make -j4` and per-module compilation
parallelises again.

Verified end-to-end: io-classes slice's GHC response file now shows
`-j4` while the captured UnitId still matches plan-nix's
`-clsss-1.8.0.1-14f3d66e`.

* v2 dev shell: pre-build SRPs, filter project pkgs, dedup by unit-id

Three fixes to `builder/shell-for-v2.nix` so the shell's cabal
store matches the user's intuition (project packages built from
the working tree, everything else pre-built):

1. Switch the default `packages` from `selectLocalPackages` to
   `selectProjectPackages`, and rename `pkgIsLocal` →
   `pkgIsProject` to use `isProject` instead of `isLocal`.
   `isLocal` covers source-repository-packages too, but SRPs are
   pinned upstream code the user doesn't iterate on — they belong
   in the shell's pre-built store, not excluded from it.

2. Filter project-package exes out of `buildToolDrvs`.  Without
   this, any project exe listed as `build-tool-depends` of another
   project package gets pre-built and dragged into the shell
   closure, along with all of its lib slices' deps (~25 stray
   slices in the cardano-wallet shell).  Mirrors v1's
   `removeSelectedInputs` and the existing `ownBuildToolSlices`
   filter.

3. Dedup the `projectPkgClosure` walk by `identifier.id` (cabal
   unit-id), not `identifier.name`.  v2 plan-nix splits each
   unit-id component into its own `hsPkgs` entry — components are
   spread across siblings — so deduping by package name dropped
   every sibling after the first and lost their deps (e.g. the
   `unit` test's `x509`/`pem` deps when `test-common` was visited
   first).

* test/cabal.project.local: bump head.hackage SHA

The `--sha256` pin for the head.hackage tar no longer matched
upstream — CI was failing with `hash mismatch in fixed-output
derivation`.  Update to the current sha256 reported by nix.

* v2 dev shell: exclude only `selectedPackages`, not all project pkgs

The previous commit treated every project package as "iterated on
from the working tree" and excluded them all from the shell's
cabal store.  That broke `tests.cabal-sublib-shell.run`, which
intentionally selects just `consumer` and expects `provider` (a
sibling project package) to be pre-built — so an unpatched cabal
inside the shell can reuse provider's lib/sublib instead of
rebuilding from source.

Mirror v1's `removeSelectedInputs` semantics: only exclude packages
in `selectedPackages` itself.  With the default
`packages = selectProjectPackages` every project pkg is selected
(so the cardano-wallet UX is unchanged); with an explicit
`packages = ps: [ ps.foo ]`, only `foo` is excluded and sibling
project pkgs land in the store.

Also drops the project-pkg closure walk — `selectedPackages`
already enumerates every unit-id fragment, so a straight
`concatMap getAllComponents` covers all components without the
walk.

* test/shell-for-setup-deps: use `ghc-pkg` exposure mode

The test runs `runghc conduit-test.hs` inside the v2 shell; that
needs `Conduit` visible to plain `ghc`/`runghc`.  Default
`exposePackagesVia = "cabal-store"` only seeds `~/.cabal/store`
(which `cabal v2-build` reads), so plain ghc still misses the
setup-deps.  Switch to `"ghc-pkg"` so the wrapped ghc stacks the
composed package.db via `GHC_ENVIRONMENT`.

* test/cabal-simple-prof: enable profiling via cabalProjectLocal

The test sets `enableProfiling = true` / `enableLibraryProfiling
= true` via haskell.nix modules.  Under v1 those translate
directly to per-component `--enable-profiling` configure flags,
but under v2 (now the default) the slice reads its toggles from
plan.json's recorded `configure-args` — and plan.json was
generated from cabal.project alone, where neither flag is set.
The slice therefore built without `-prof`, and `+RTS -p` failed
with "the flag -p requires the program to be built with -prof".

Inject `profiling: True` / `library-profiling: True` at project
level via `cabalProjectLocal` so plan-nix records
`--enable-profiling` / `--enable-library-profiling`, matching the
slice's actual build and keeping the slice's UnitId
reproducible.  The module-level overrides are kept so v1 still
builds with profiling too.

* v2 builder: support per-package configure-args

Previously `projectConfigPragmas` read configure-args off the
first configured plan entry and emitted them under a single
`package *` block, on the assumption that "the same flags appear
in every configured plan entry's configure-args".  That holds for
project-wide toggles (--enable-optimization, --disable-shared,
...) but breaks the moment a user sets something per-package —
e.g. `package cabal-simple\n  profiling: True`.  The setting only
landed on cabal-simple's plan entry; if the first configured
entry was a transitive dep, the slice's cabal.project never saw
`profiling: True` and the exe built without `-prof`.

Group plan entries by `pkg-name`, take the union of pragmas
across each pkg's units (cabal.project only supports per-package
granularity), then split into:

  * `package *` — pragmas common to *every* pkg-name.  The slice's
    `cabal v2-build` resolves transitive hackage deps fresh, so
    these have to land project-wide for the deps' UnitIds to line
    up with plan-nix.

  * `package <name>` — for each pkg-name with pragmas extending
    the baseline (e.g. `package cabal-simple` getting
    `profiling: True` from
    `packages.cabal-simple.enableProfiling = true` mirrored into
    cabal.project).

`tests.cabal-simple-prof.run` now uses the per-package shape that
mirrors haskell.nix's modules:

  package *
    library-profiling: True
  package cabal-simple
    profiling: True

* v2 builder: run hpack on local hpack-using packages

When a project has `supportHpack = true` and a local package
ships only `package.yaml` (no `.cabal`), v1 ran hpack at the
component-build phase to generate the `.cabal`.  v2 never ran
it, so the v2 source tarball had no `.cabal` and the
`v2-exe-repo` pre-build step failed with `tar:
<pkg>-<ver>/<pkg>.cabal: Not found in archive`.

Thread `cabal-generator` through `hspkg-builder.nix` to
`comp-v2-builder.nix` and run hpack inside the staged source
dir before tarballing, mirroring v1's
`comp-builder.nix:485` invocation.  Skipped when the package
isn't local (hackage tarballs already contain a generated
`.cabal`) or when `cabalFile` is set (e.g. an X-revision from
`package-description-override`, which provides its own `.cabal`).

Verified against `tests.cabal-hpack.run`.

* test/cabal-sublib: restore ghcjs `broken` marker

Re-enables the `broken = isGhcjs && ghc >= 9.6.1` guard that
commit 19b9a5110 commented out while bringing up v2.  The
underlying failure is in ghcjs/emscripten wasm-ld ("section too
large" linking the `-O`-optimized C-backend objects), which
predates v2 and isn't a slicer issue.  Skip the test on ghcjs
again so CI doesn't fail on a pre-existing upstream limitation.

* test: move HsOpenSSL pointer-types workaround to cabal.project.local

`packages.HsOpenSSL.ghcOptions` previously held a
`-optc=-Wno-incompatible-pointer-types` workaround.  v2's slices
read configure-args from plan.json; module-level `ghcOptions`
never reach plan-nix's plan.json, so under v2 the slice picked up
the flag (via comp-v2-builder's per-package ghcOptions block) but
plan-nix did not — and the resulting UnitIds diverged
(`HsOpnSSL-0.11.7.10-9ebdbccd` expected vs `-ecc34d19` produced).

Move the workaround into `test/cabal.project.local` under
`package HsOpenSSL ghc-options:` so plan-nix and the slice agree.
Drop the now-unneeded `HsOpenSSL` entry from `package-keys` in
`test/modules.nix`.

Verified against `tests.exe-dlls.build`.

* test/shell-for: use `ghc-pkg` exposure for runghc

The test runs `runghc conduit-test.hs` against `env.ghc` /
`envDefault.ghc`.  With v2's default
`exposePackagesVia = "cabal-store"` only `~/.cabal/store` is
seeded; plain `runghc` doesn't read the cabal store, so it failed
with "Could not find module 'Conduit'".  Switch the three shells
to `"ghc-pkg"` so `env.ghc` is wrapped to stack the composed
package.db via `GHC_ENVIRONMENT`.  Mirrors the earlier fix in
`tests.shell-for-setup-deps`.

* v2 dev shell: speed up haskell-nix-cabal-store-sync

The script ran a per-conf `diff -q` and a per-unit `diff -qr` on
every shell entry — for cardano-wallet's ~240 slices that meant
forking hundreds of `diff` invocations and walking each unit's
file tree.  Two short-circuits:

  * Per-composedStore marker file at
    `~/.cabal/store/.haskell-nix-shell-markers/<basename-of-src>`,
    written after a successful run.  On re-entry the marker is
    present and the script exits before the scan.  Different
    shells have distinct basenames, so alternating shells don't
    invalidate each other's markers.

  * Readlink fast-path in the scan: confs and lib files are
    installed as symlinks to `$src/...`, and unit dirs are lndir
    trees of symlinks under `$src/$ghcName/$unitId/`.  When the
    target is already in the right shape, skip the per-file `diff`
    fork.  Only when the cheap check fails does the script fall
    back to `diff -q` / `diff -qr` to detect genuine conflicts.

Measured on cardano-wallet's shell (~240 slices, fresh CABAL_DIR):
first run 24.5s, second run 0.012s.

* v2 builder: surface HPC artefacts and src dir for cover.nix

Three incremental improvements toward making `lib/cover.nix` work
against v2 lib/test slices.  The end-to-end coverage test still
fails because v2's lib slices use a hashed UnitId
(`pkgb-0.1.0.0-<hash>`) while a sibling test slice rebuilds the
lib inplace and emits `.tix` referencing `pkgb-0.1.0.0-inplace`,
so HPC can't match them up.  That alignment is a deeper change;
land the supporting plumbing first:

  * Add `srcSubDir` / `srcSubDirPath` to v2 slice passthru so
    `lib/cover.nix:19`'s `map (l: l.srcSubDirPath) mixLibraries`
    no longer hits "attribute missing".

  * Library slices now copy each `extra-compilation-artifacts/hpc/<way>/{mix,tix}`
    subdir up to `$out/share/hpc/<way>/{mix,tix}/<pkg>-<ver>/`
    (the predictable name `lib/cover.nix` and the cover-report
    tests expect — v1 does this too in `comp-builder.nix:831`).

  * v2's `lib/check.nix` branch copies `<exeName>.tix` from the
    test run to `$out/share/hpc/vanilla/tix/<exeName>/`, mirroring
    v1's `lib/check.nix:119`.

* v2 builder: error on `.profiled` with migration hint

v1 slices provided `.profiled` as an overlay rebuild with
`enableLibraryProfiling = true`.  v2 reads configure-args from
plan.json, so an overlay would emit `--enable-profiling` toggles
that plan-nix doesn't know about and the slice's UnitId would
diverge.

Replace the (missing) attribute with a `throw` that points users
at `cabal.project` / `cabalProjectLocal`:

    package <pkgname>
      profiling: True
      library-profiling: True

so plan-nix records the toggles and the slice's UnitId stays
aligned.  Set the `throw` at both the top level and inside
`passthru` because mkDerivation only lifts passthru → top level at
derivation creation time; post-hoc `// { passthru = ... }` doesn't
re-lift.

Adds `docs/dev/profiling.md` with the migration recipe.

* configure-args: pick up profiling/coverage from plan.json

Plan-to-nix's `modules/install-plan/configure-args.nix` already
translates `--ghc-option=` and `--configure-option=` entries from
plan.json's per-pkg `configure-args` into haskell.nix module
options.  Extend it to also pick up the `--enable-{profiling,
library-profiling, coverage}` toggles so v1's `comp-builder`
(which reads `enableProfiling`, `enableLibraryProfiling`,
`doCoverage` straight off the component) honours
`package <pkg>\n  profiling: True` set in cabal.project — without
needing module-level overrides.

v2 already reads these from plan.json directly via its own
`projectConfigPragmas`; the picked-up values are merely
consistent there.

Migrate the `tests.{exe,exe-lib,th}-dlls.{check-,build-}profiled`
variants to a sibling project whose `cabalProjectLocal` enables
profiling — the v2 builder no longer ships `.profiled` as an
overlay rebuild (see `docs/dev/profiling.md`).

* v2 builder: align slice/check derivation names with v1

Drops the `cabal-slice-` prefix and reorders the slice's `name`
attribute from `<pkg>-<ver>-<ctype>-<cname>` to v1's shape
`<pkg>-<ctype>-<cname>-<ver>` (mirrors `comp-builder.nix:268`).
Same for the auxiliary `check-*` and `store-*` derivations.

Callers that hardcoded v1 derivation names — most prominently
`tests.coverage.run`, which checks for
`tix/pkgb-test-tests-0.1.0.0-check/tests.tix` — keep working
without per-test edits when projects flip
`builderVersion = 1 → 2`.

Also surface the lib slice's `.mix` files under
`<pkg>-<ver>-inplace/<Module>.mix` so they line up with the
inplace UnitId that test-slice-built `tests.tix` files reference
(`Tix [ TixModule "<pkg>-<ver>-inplace/<Module>" … ]`); the .mix
content itself is byte-identical between the lib slice's
hash-named UnitId build and the test slice's inplace rebuild.

Move the coverage-test's `doCoverage` modules into
`cabalProjectLocal` (`package <pkg>\n  coverage: True`) so v2's
plan-nix records `--enable-coverage` and the slice actually
produces .mix/.tix output.  The module-level overrides are kept
for the v1 builder.

Verified against `tests.coverage.run`.

* v2 builder: drop \`.jsexe/all.js\` exeExt under cabal v2-build

v1 (Setup.hs install) for ghcjs preserves the
\`bin/<exe>.jsexe/all.js\` directory layout, which is why
\`comp-builder.nix:450\` appends \`.jsexe/all.js\` to the exe name
for \`isGhcjs && ghc < 9.8\`.  v2 builds via \`cabal v2-build\`,
whose install step bundles the \`.jsexe/\` contents into a single
self-contained \`#!/usr/bin/env node\` script at \`bin/<cname>\` —
there is no \`.jsexe/\` directory in the slice's output.  Use
the bundled-file path so v2's \`exePath\` / \`find\` lookup land
on the file cabal actually produced.

Verified against \`aarch64-darwin.unstable.ghc967.ghcjs.hello\`
(slice surfaced \`bin/hello\` cleanly, no placeholder fallback).

* v2 builder: trim dist-newstyle/ from slice $out

The slice's `$out/dist-newstyle/` carried cabal's source tarballs
and the build tree — hundreds of MB to GB per slice for nontrivial
projects.  Nothing downstream of a successfully-built slice reads
it: subsequent slices only pull from `$out/store/`, and the
diagnostic `checkAgainstPlan` is a separate derivation with its
own dist-newstyle.

Trim it at the end of `installPhase`, after
`comp-v2-builder.nix`'s test/bench install step (which does need
to find the unpinged binary in `dist-newstyle/build/.../<exe>`).
Lift `cache/plan.json` to `$out/plan.json` so it stays available
for human debugging — the rest of `cache/` is internal.

Verified on the ghc967.ghcjs.hello slice: $out shrinks from
~7.9GB to ~1MB, with `bin/`, `store/`, `plan.json`, `unit-ids`,
and `nix-support/` retained.

* test/with-packages: use \`project.shellFor\` instead of \`comp.shell\`/\`comp.env\`

v2 slices don't expose v1's per-component \`.shell\` / \`.env\`
attributes — \`shell\` is provided at the project level via
\`project.shellFor\`, and that mode covers what this test needs
(a wrapped \`ghc\` / \`runghc\` that can see the package's deps).
\`exposePackagesVia = "ghc-pkg"\` makes \`shell.ghc\` a wrapped
GHC stacking the composed package.db via \`GHC_ENVIRONMENT\`, so
\`runghc ./Point.hs\` and \`ghc Point.hs\` resolve the deps the
same way v1's \`library.env\` did.

Works under both v1 and v2 — no per-component shell required.

* test: migrate `.profiled` callers to sibling cabal.project

v2 slices no longer ship `.profiled` as an overlay rebuild — see
`docs/dev/profiling.md`.  Migrate the remaining tests that
referenced `…components.<kind>.<name>.profiled`
(`js-template-haskell`, `th-dlls-minimal`, `gi-gtk`) to a sibling
project whose `cabalProjectLocal` enables profiling, matching the
shape already used by `exe-dlls` / `exe-lib-dlls` / `th-dlls`.

Expose each new project's `plan-nix` under a distinct
`ifdInputs` key so the materialised plan-nix expressions are
covered by CI:

  plan-nix             — non-profiled (default)
  plan-nix-ei          — externalInterpreter variant (th-dlls /
                          th-dlls-minimal)
  plan-nix-profiled    — profiled
  plan-nix-profiled-ei — profiled + externalInterpreter

* plan-nix: gate `-inplace` on GHC ≥ 9.8

GHC 9.8 added an `-inplace` suffix to its boot-package UnitIds
(`base-4.19.2.0-inplace` vs `base-4.18.3.0` on 9.6) and started
emitting a `Project Unit Id` field in `ghc --info`.  The dummy
ghc/ghc-pkg in `lib/call-cabal-project-to-nix.nix` was hardcoding
both, so cabal computed UnitIds against the dummy with
`-inplace` even on GHC 9.6.  When the slice's real ghc 9.6 then
returned ids without `-inplace`, the slice's UnitId for any
package depending on a boot package (`colour` ↔ `base`) diverged
from plan-nix and the slice failed its UnitId check.

Verified empirically that `-inplace` appears starting at GHC 9.8:
  ghc967  → id: base-4.18.3.0
  ghc984  → id: base-4.19.2.0-inplace
  ghc9103 → id: base-4.20.2.0-inplace
  ghc9124 → id: base-4.21.2.0-inplace
  ghc9141 → id: base-4.22.0.0-inplace

Make both the `Project Unit Id` field and the `-inplace` suffix
conditional on `versionAtLeast ghc.version "9.8"`.

Verified against `tests.shell-for.env` on ghc967 (which exercises
the colour slice that was failing in CI).

* v2 builder: expose `.doc` via sibling `cabal v2-haddock` slice

Under v1 every component's `.doc` was a sibling Setup haddock
derivation that shared the lib's UnitId.  Under cabal v2-build
this no longer holds: `cabal v2-haddock` flips
`elabBuildHaddocks` (and the haddock-html / haddock-hscolour /
... family) on every unit's `ElaboratedConfiguredPackage`, which
all land in `pkgHashConfigInputs`.  Calling `cabal v2-haddock`
against a plan that didn't already have `documentation: True`
forks every UnitId in the closure and triggers a from-source
rebuild.

Round-tripping `documentation: True` through plan.json's
`--ghc-option=-haddock` / `configure-args.nix` /
`ghc-options: -haddock` is NOT equivalent: the ghc-option keeps
haddock comments in `.hi` files but doesn't set the haddock-config
booleans, so the slice's `pkgHashConfigInputs` diverges from
plan-nix's and the dep closure's UnitIds fork (observed on
OneTuple in `tests.sublib-docs`: plan-nix `9a847723` vs slice
`f546bd36`).

Approach:

  * `comp-v2-builder.nix` detects per-package `--ghc-option=-haddock`
    in plan.json (the `documentation: True` signal cabal-install
    surfaces) and emits `package <pkg>\n  documentation: True\n`
    in the slice's cabal.project for every documented package.
    `-haddock` is filtered out of the per-pkg `ghc-options:` block
    so cabal doesn't see it twice in `pkgHashGhcOptions`.

  * Each library slice exposes `.doc`, a sibling derivation that
    runs `cabal v2-haddock` against the already-built unit (no
    closure rebuild).  `.doc` throws with a migration hint when
    documentation isn't in the project's plan-json — that's the
    only shape where the UnitIds align.

  * Doc slices propagate `(map d: d.doc)` for `docEnabled` deps
    only, so cross-package hyperlinks resolve.  Mixed projects
    (some packages docs, others not) keep working because non-doc
    deps come in as plain slices.

  * `build-cabal-slice.nix` keeps cabal's native unit-dir layout
    (`$out/store/<ghc>/<unit-id>/share/doc/html/`) so doc slices
    `lndir` into `~/.cabal/store/` as drop-in replacements, and
    cross-package hyperlinks (absolute
    `file:///nix/store/<doc-slice>/store/<ghc>/<dep-uid>/...`)
    resolve back into the slice's own tree.  Non-target unit
    haddocks are stripped from `$out` to keep each slice lean —
    deps' html lives in the deps' own `.doc` slices.

`docs/dev/haddock.md` documents the v2 semantics, the
`documentation: True` requirement, and why there's no
`slice.haddockDir` (local plan-nix UnitIds use `<pkg>-<ver>-inplace`
form while the slice's cabal-store uses cabal's mangled hash form,
so there's no eval-time-stable html path — callers `find` it
under the doc slice).

Verified `tests.sublib-docs.run` builds Lib.html and Slib.html
under `slice.doc`, and OneTuple's UnitId in plan.json matches
plan-nix's recorded id.

* v2 builder: fix `ghcShim` collision when cross GHC ships unprefixed tools

GHC 9.14.1's `armv7a-android` cross GHC ships *both*
`<prefix>deriveConstants` and an unprefixed `deriveConstants` in
its `bin/` (the latter for build-host use).  `ghcShim`'s
single-pass loop iterated `${ghc}/bin/*` alphabetically, hit
`armv7a-...-deriveConstants` first, and the case branch
synthesised an unprefixed-fallback alias
`$out/bin/deriveConstants -> <prefix>deriveConstants` (the
`[ -e ]` guard was for that fallback, not the raw link).  When
the loop later reached the real `deriveConstants`, the raw
`ln -s "$f" "$out/bin/$base"` crashed with "File exists".

Earlier cross GHCs only shipped `<prefix>deriveConstants`, so
the fallback was the sole producer of `$out/bin/deriveConstants`
and never collided.  GHC 9.14.1 added the unprefixed sibling and
exposed the race.

Switch to a two-pass shape: pass 1 links every source bin/ entry
under its own name, pass 2 synthesises unprefixed aliases only
for prefixed names that have no real unprefixed sibling.  Real
files always win; aliases only fill genuine gaps.

Same fix in both `builder/build-cabal-slice.nix` and
`builder/shell-for-v2.nix` (which carries an identical shim for
the dev shell's cross-cabal wrapper).

* Replace post-plan ghc.src override with project-level useLocalGhcLib

`modules/configuration-nix.nix` used to unconditionally `mkForce`
`packages.ghc.src` to a symlinkJoin of `(configured-src + generated)`
under `compiler/`, redirecting `lib:ghc`'s source to the local GHC
tree *after* plan-nix had been computed against hackage's
`ghc-X.Y.Z.tar.gz`.  v1 (Setup.hs) didn't care — it builds whatever
`src` it's given.  v2 forks the slice's UnitId from plan-nix's
recorded one (the slice rebuilds from a different source than the
planner saw) and the slice fails its UnitId check.

Replace with an opt-in project-level flag, `useLocalGhcLib`, that
exposes the GHC compiler tree to the planner up-front.  Mechanism
differs by project type:

  * Cabal projects (modules/cabal-project.nix) inject a
    `source-repository-package` block + `allow-boot-library-installs:
    True` into `cabalProjectLocal`, with an `inputMap` entry keyed
    by `<url>/<ref>` (string-context-stripped) so haskell.nix
    short-circuits the `builtins.fetchGit` path.  Plan-to-nix and
    the v2 slice both see the same wrapped repo, cabal hashes the
    same content into `pkg-src-sha256`, and UnitIds align.

  * Stack projects (modules/stack-project.nix) re-add the
    `packages.ghc.src` override as a contributed module, gated on
    `useLocalGhcLib`.  Stack-to-nix's input shape doesn't fit the
    cabal source-repo path; instead we lean on stack only supporting
    v1 for now (`builderVersion = mkForce 1` in stack-project.nix's
    config) — v1 doesn't enforce UnitId alignment, so the post-plan
    swap is fine.

The option itself lives in `modules/project-common.nix` so both
project types see it without duplication.

`builder/comp-v2-builder.nix` emits `allow-boot-library-installs:
True` in the slice's cabal.project whenever the target or any non-
pre-existing lib dep is on cabal-install's hard-coded
non-reinstallable list (`ghc`, `template-haskell`, `Cabal`,
`Cabal-syntax`, `ghc-prim`, `ghc-bignum`, `ghc-boot`,
`ghc-boot-th`, `ghc-heap`, `base`, `ghci`, `ghc-internal`, `rts`).
Without it the slice's solver rejects the source instance with
"constraint from non-reinstallable package requires installed
instance" once `ghc` is being source-built.

`test/ghc-lib-reinstallable/cabal.nix` and
`test/ghc-lib-reinstallable/stack.nix` opt in via
`useLocalGhcLib = true`.  The stack test's resolver is bumped to
`nightly-2026-05-03` (latest known to the pinned stackage.nix at
the time of this commit).

Verified end-to-end:
  * `tests.ghc-lib-reinstallable-cabal.run` (v2, ghc9141) — passes
  * `tests.ghc-lib-reinstallable-cabal.run` (v1, ghc9141) — passes
  * `tests.ghc-lib-reinstallable-stack.run` (v1, ghc9124) — passes

* cabal-project: inject `executable-static: True` for musl hosts

Plan-to-nix's cabal-install records `--disable-executable-static`
in `configure-args` for every component when the project hasn't
explicitly enabled it.  v1's `builder/comp-builder.nix:384`
papered over this by injecting `--ghc-option=-optl=-static` (and
`-optl=-pthread`) at comp-builder time — outside plan.nix's view.
v1 doesn't enforce UnitId alignment with plan.json, so the
post-plan flag insertion is harmless there.

Under v2 the slice mirrors plan.json's `configure-args` exactly,
so without something at the project level the slice links
dynamically and the `tests.c-ffi.run` musl assertion (`grep "not
a"` on glibc-ldd output) fails.

Inject `executable-static: True` into `cabalProjectLocal` when
`stdenv.hostPlatform.isMusl`.  Plan-to-nix then records
`--enable-executable-static`, v2's
`comp-v2-builder.nix:projectConfigPragmas` round-trips it through,
and cabal links the executable statically.  v1 is unaffected — its
post-plan `-optl=-static` injection still fires, redundant but
harmless.

Verified by running `tests.c-ffi.run` on the
`x86_64-linux.unstable.ghc9141.musl64` jobset
(commit `d027fbe4e` + this patch in a fresh worktree).

* dummy-ghc: add Android branch matching real cross-ghc capabilities

`dummy-ghc` is the eval-time stand-in cabal-install runs against
during plan-to-nix.  Its `--info` output shapes plan.json's
per-unit `configure-args` (which feed `pkgHashConfigInputs` and
therefore the UnitId).  When the dummy reports different
capabilities than the real cross-ghc the slice rebuilds with,
plan-nix and slice compute different UnitIds and the slice's
expected-package check fails.

The "otherwise" branch (Linux / Darwin / native) reports
`Support shared libraries: YES`, `GHC Dynamic: YES`, RTS ways
including the full `_dyn` family, and `Stage: 2`.  Real Android
cross-ghc (`aarch64-unknown-linux-android-ghc 9.14.1`) is a
stage-1 NDK build with no `_dyn` ways and no `Support shared
libraries` field at all (its `GHC Dynamic` is `NO`).  Falling
through to "otherwise" made plan-nix record `--enable-shared`
for Android packages while the slice's real ghc silently flipped
to `--disable-shared`, forking the UnitId on
`pkgHashSharedLib`.

Add an explicit Android branch.  Reference values cross-checked
against `aarch64-unknown-linux-android-ghc --info` on the live
9.14.1 cross GHC derivation; key fields:
  * Support dynamic-too: YES   (GHC's flag, kept like real ghc)
  * GHC Dynamic: NO            (was YES — drove --enable-shared)
  * RTS ways: no `_dyn` family
  * Stage: 1
  * No `Support shared libraries` field (cabal interprets
    absence as no-shared-libs, matching real ghc)

Verified `tests.extra-hackage` slices for
`x86_64-linux.unstable.ghc9141.aarch64-android-prebuilt` no
longer fail the slice's UnitId-alignment check.  (The `.run`
step still fails because qemu-aarch64 can't load Android's
`/system/bin/linker64` to actually exec the binary — that's a
pre-existing runtime issue, not a v2 alignment problem.)

For musl, real and dummy ghc already agree
(`Support dynamic-too: YES, GHC Dynamic: YES`); the
`executable-static: True` injection in
`modules/cabal-project.nix` stays — it's a user-opt-in flag,
not capability-derived from `ghc --info`.

* shellFor: route through buildPackages so cross targets evaluate

`shellFor` (both v1 and v2) ran into "no C compiler provided for
this platform" under cross targets where the host stdenv has no
cc — most visibly ghcjs, also wasm and android-prebuilt.  Three
distinct issues, all fixed here:

1. `mkShell` itself was `pkgs.mkShell` (host-stdenv-bound).
   `pkgs.bashInteractive`'s eval reads `stdenv.cc.isClang`, and
   under ghcjs cross there is no cc → throw.  Pass
   `mkShell = pkgs.buildPackages.mkShell` to both shellForV1 and
   shellForV2 so the shell drv lives on the build platform
   regardless of the host (the user opens the shell on their
   build host anyway).

2. `pkgs.runCommand` was building `packageEnv` and `wrappedGhc`
   (the wrapped GHC the shell exports as `shell.ghc`) against
   the cross stdenv.  Once these landed in the shell's
   `nativeBuildInputs`, mkShell's dep-splicing eventually
   re-evaluated the cross stdenv's bash via setup hooks, hitting
   the same cc.isClang.  Switch both to
   `pkgs.pkgsBuildBuild.runCommand` and `pkgs.pkgsBuildBuild.makeWrapper`
   so the wrappers themselves are build-platform drvs.  (Native
   builds are unaffected — `pkgsBuildBuild == pkgs` there.)

3. `store = composedStore` was a top-level attr to mkShell.
   Recent nixpkgs's `mkDerivation` folds unknown top-level attrs
   into `env` and type-checks each value, and `isDerivation
   composedStore` forces evaluation of the cross stdenv's bash
   the same way.  Move `store` into `passthru.store` — mkDerivation
   lifts passthru into the result drv anyway, so `shell.store`
   continues to resolve.

Verified `tests.with-packages.test-shell` now evaluates under both
the `aarch64-darwin.unstable.ghc9141.ghcjs` (cross) and `.native`
jobsets, and `shell.store` still resolves on both.

* dummy-ghc: extend Android branch to cover pkgsStatic

The Android dummy-ghc branch added in 491a91d13 reports a stage-1
GHC built without dynamic support: no `_dyn` RTS ways,
`GHC Dynamic: NO`, no `Support shared libraries` field.  The same
shape applies to nixpkgs's `pkgsStatic` (the haskell.nix `static`
jobset) — its real GHC is also built without dynamic support.
The only difference is `Stage`: Android cross is stage-1, the
static GHC is stage-2.

Without this branch, plan-to-nix records `--enable-shared` for
static-target packages while the slice's real ghc silently flips
to `--disable-shared`, forking the UnitId on `pkgHashSharedLib`
and breaking the slice's expected-package check.

Extend the Android branch's predicate to
`isAndroid || isStatic` and emit the right `Stage` per platform.
Reference: `pkgsStatic.haskell-nix.compiler.ghc9141 --info` on
the live `x86_64-unknown-linux-musl` derivation.

Verified: `tests.js-template-haskell.check` on
`x86_64-linux.unstable.ghc9141.static` builds end-to-end (slices
align, the test exe links statically as expected, the `.check`
derivation completes).

* dummy-ghc: extract to its own file + add cross-compiling field + test

Move the eval-time `dummy-ghc` script construction from
`lib/call-cabal-project-to-nix.nix` to a standalone
`lib/dummy-ghc.nix` so it can be tested independently.

Add a new test `tests.dummy-ghc-info` that diffs `dummy-ghc --info`
against the real GHC's `--info` (after stripping nix-store paths
and a hand-curated list of fields cabal-install does not consult
for elaboration / unit-id hashing).  The test fails on any
remaining divergence, surfacing where `dummy-ghc.nix` needs to
mirror real GHC more closely.

Two divergences caught and fixed by the new test on the native
ghc9141 jobset:

  * Real GHC always emits `("cross compiling","YES"/"NO")`; dummy
    didn't.  Cabal-install reads this field to decide whether to
    apply cross-toolchain detection.  Add it, gated on
    `buildPlatform.config != targetPlatform.config`.

  * Real GHC 9.14.1 omits the legacy `Support shared libraries`
    field on native targets — cabal infers shared-lib support from
    `Support dynamic-too` + `RTS ways`.  Dummy was emitting
    `("Support shared libraries","YES")` in its "otherwise" branch,
    creating a phantom field real GHC doesn't have.  Drop it.

Plus minor cosmetic: dummy emitted the alist's first entry as
`("target os", "OSDarwin")` (with a space after the comma); real
GHC omits the space.  Tighten dummy to match.

`tests.dummy-ghc-info` is registered alongside other tests in
`test/default.nix` so it runs across every cross-target jobset
(armv7a-android, x86_64-musl, ghcjs, wasi, mingwW64, …).  Future
divergences land as test failures rather than silent UnitId forks
between plan-nix and the v2 slice.

* dummy-ghc: report Host=buildPlatform; fix ghcjs/wasm interpreter

Driven by `tests.dummy-ghc-info` failures.  Two fixes:

  * Real cross-GHC reports `Host platform = buildPlatform` (where
    ghc itself runs), NOT the cross-target platform.  `pkgs.stdenv.hostPlatform`
    in a cross-pkgs context IS the cross target, so the previous
    `Host platform = pkgs.stdenv.hostPlatform` was wrong.  Use
    `buildPlatform` for both `Build platform` and `Host platform` —
    `Target platform` carries the cross target.

  * ghcjs / wasm cross-GHCs report `Have interpreter: NO`,
    `Use interpreter: NO` (the JS / WASM backends don't have a
    GHCi-style bytecode interpreter).  Dummy was emitting `YES`
    for both.  Flip them to match.

Verified `tests.dummy-ghc-info` passes on the
`aarch64-darwin.unstable.ghc9141.{native,ghcjs}` jobsets.  The
test will now catch any future divergence between dummy-ghc and
real GHC's `--info` output.

* v2 builder: route exe-depends closure walk through pkgsBuildBuild's plan

Build-tool exes (hsc2hs / alex / happy / ...) run on the build
platform; their unit-ids and lib-dep closures belong to the
build-build project's plan, not the cross plan.  Previously
\`mkClosureFrom\` resolved every \`exe-depends\` id against the cross
plan-json — pulling in cross-target unit-ids and (transitively) the
wrong versions of process / directory / etc. for the slice's
\`libConstraintPins\` and \`sourceRepoEntries\`.

Tag every closure node with its plan ("cross" or "bb"), and have
\`exe-depends\` ids cross over to the bb plan via direct id lookup
(rare hit) or pkg-name fallback through \`package-ids-by-name\`.
Lib-deps of bb-tagged nodes stay in bb-plan; lib-deps of cross-tagged
nodes stay in cross.  \`sourceRepoEntries\` now dispatches on
\`e.plan\` for the \`hsPkgs\` lookup.

\`libConstraintPins\` reverts to \`allDepClosure\` so cabal in the
slice pins the bb plan's versions for an exe's lib closure (matching
what the bb slice was built against).

Plumbed via \`hsPkgs.{plan-json-by-id, package-ids-by-name}\` exposed
in component-driver — \`hsPkgs.pkgsBuildBuild.<idx>\` reaches the
bb project's indexes naturally.

* v2 builder: skip exe entries when forming libConstraintPins

The bb-plan walk added by the previous commit pulled exe entries
(hsc2hs / alex / happy / ...) into `allDepClosure`.  Pinning them
in `libConstraintPins` lands them in `extra-packages:`, which
makes cabal's solver in the slice plan the build-tool from source
instead of using the pre-installed exe slice.  Skip closure
entries whose `component-name` starts with `exe:` — their lib
closures (process, directory, ...) still get pinned via the
`libDepsOf` walk in bb-plan.

* v2 builder: drop build-tool source tarballs from the slicing repo

Build-tool exes (hsc2hs / alex / happy / ...) reach the slice's
build environment via `extraNativeBuildInputs` (PATH).  Their
source tarballs were also being added to the slicing repo's index
via `++ lib.concatMap (s: s.passthru.transitiveTarballs) buildToolSlices`,
which let cabal's solver see hsc2hs in the index and plan a
from-source rebuild for `build-tool-depends: hsc2hs:hsc2hs` —
forking the tool's unit-id (cross GHC info ≠ build-platform GHC
info) and failing the expected-package check.

Drop the line and rely on cabal's legacy PATH fallback for build-tool
resolution.  The tool's lib closure (process, directory, ...) still
reaches the slicing repo when those libs are also lib-deps of the
target package; otherwise they're not needed (the slice doesn't
re-solve the tool).

* v2 builder: split sibling exe-deps from lib-deps

The v2 slice handles sibling `depends` and `exe-depends` from
plan-json differently:
  * lib-deps land in `externalDepIds`, drive the slicing repo
    (via `depTransitiveTarballsOf`), and get pinned in
    `libConstraintPins`.
  * exe-deps (build-tools) need their exe on PATH but their
    source MUST stay out of the slicing repo's index — otherwise
    cabal's solver in the slice plans the tool from source for
    `build-tool-depends: foo:foo`-style deps and forks the
    tool's unit-id (cross GHC info ≠ build-platform GHC info).

Split the plan-json walk in hspkg-builder into `homeDependIds`
(lib) and `homeBuildToolIds` (exe).  The v2 builder's
`homeDepExeSlices` now feeds from the latter; `externalDepIds`
naturally drops exe-only entries.

* hspkg-builder: hoist homeIds out of comp-v2-builder attr-set

Pure restructure: `inherit (homeIds) ...` only works when `homeIds`
is in scope, not as a sibling field of the same attr-set.  Move
the let-binding above the `comp-v2-builder { ... }` call so the
inherit references resolve.

* v2 builder: cross builds steer cabal off build-tool goals via --with-PROG

On native, build-tool source tarballs go in the slicing repo (as
before) so cabal recognises the pre-installed bb slice's unit-id
and skips re-building.  Restored to old behaviour, including
merging `homeBuildToolIds` into `externalDepIds`.

On cross the unit-ids legitimately diverge (cross GHC info ≠
build-platform GHC info), so the bb hsc2hs slice would never
match what cabal-in-slice computes; cabal would plan it from
source and fork.  Instead:

  * keep the build-tool's source OUT of the slicing repo
    (`externalDepIds` skips `homeBuildToolIds`; `depTransitives`
    skips the `buildToolSlices` tarballs);
  * emit `--with-<exe>=<bb-slice>/bin/<exe>` for every transitive
    build-tool reached in `allDepClosure` — cabal skips planning
    the tool from source and uses the explicit path;
  * skip the unit-id MATCH check (`expectedUnitId = null`), but
    `build-cabal-slice` now verifies exactly ONE unit-id is
    captured so the slice still produces a single, well-defined
    `$out/store` content.

* v2 builder: collapse duplicate isCross definition

The hoisted-up isCross collides with the existing one further
down.  Drop the lower-down one.

* v2 builder: keep build-tool tarballs in the slicing repo on cross too

The cabal solver in the slice always plans every transitive
`build-tool-depends:` package, even when `--with-PROG=PATH` is
set — `--with-PROG` only short-circuits at build time, after the
solver has succeeded.  Leaving the build-tool's source out of the
index made the solver fail with
`unknown package: <pkg>:<exe>:exe.<exe> (dependency of ...)`.

Restore the build-tool's transitive tarballs (the tool itself
plus its lib closure) for every build.  On native cabal recognises
the pre-installed bb slice's unit-id and skips re-building; on
cross `withProgFlags` provides `--with-<exe>=<bb-slice>/bin/<exe>`
so cabal short-circuits the build despite the divergent unit-id.

The dry-run `expectedPackage` check is skipped on cross (cabal
*plans* every build-tool there, by design), leaving the
post-install captured-unit-ids count==1 check as the guarantee
that cabal did short-circuit the tool builds.

* v2 builder: compose cross build-tool slice (matching uid) on cross

Build-tool slices are the unit cabal needs to find in the
cabal-store to skip re-building.  Previously we composed the
build-build slice (e.g. native hsc2hs), whose plan-nix-computed
uid Y doesn't match what the cross slice's cabal computes for the
tool (uses cross dummy-ghc info, matching real cross-ghc info per
tests.dummy-ghc-info).  cabal didn't recognise the composed slice
and rebuilt the tool from source.

Compose the CROSS hsPkgs's tool slice instead (`targetSlice` in
the new `transitiveBuildToolEntries`).  Its uid matches what the
slice's cabal computes, so cabal recognises the tool as already
installed and skips re-building.  The cross binary isn't runnable
on the build host, but cabal only needs the unit-id-keyed dir
present in the cabal-store; actual invocation goes through
`--with-PROG=<buildSlice>/bin/<exe>` which points at the
build-build binary.

Skip the expected-uid check on cross (`expectedUnitId = null`):
the slice's network unit-id picks up `--with-PROG=PATH` into
pkgHashProgramArgs, which plan-nix doesn't have, so the two uids
legitimately diverge.  The post-install captured-set check now
verifies the slice produced exactly one unit for the target
package plus zero-or-more units for the allowed transitive
build-tools (a safety net for cases where cabal didn't recognise
a composed targetSlice's uid).  The dry-run plan check similarly
tolerates build-tool packages — anything else is still a fail.

* v2 builder: tighten unit-id pkg-name parser to a strict version pattern

`iserv-proxy-9.3-011cc258...`'s hash starts with `0`, which the
old `^[0-9]` regex matched, leaving the parser thinking the hash
was the version and producing `iserv-proxy-9.3` as pkg-name.
Use `^[0-9]+(\.[0-9]+)*$` so only true version tokens match
(the hash, being all-hex with mixed letters, never matches).

* flake.lock: bump iserv-proxy to a31c4c4 (armv7a NDK stubs)

Picks up `stable-haskell/iserv-proxy@a31c4c4`:
  armv7a-android: stub symbols missing from current NDK

so the v2 cross builder's `iserv-proxy-interpreter` exe slice
gets past lld's undefined-symbol errors on armv7a-android.

* flake.lock: bump iserv-proxy to 8cdc446 (posix_spawnp stub)

* v2 builder: inject component.libs paths into post-register .conf

GHC's runtime linker (the one TH eval uses, including under
`-fexternal-interpreter` / `ghc-iserv`) dlopens each loaded
package's `extra-libraries:` entries by bare name (e.g.
HsOpenSSL's `extra-libraries: ssl crypto` → `libssl.so`).
Cabal records empty `library-dirs:` etc. in the per-unit
`.conf` because we deliberately don't push these paths into
the slice's configure args (that would land in
`pkgHashExtraLibDirs` and fork the slice's unit-id from
plan-nix).

v1 baked these into the .conf via `make-config-files.nix`'s
`extra-lib-dirs` flag fed to `Setup configure` (v1 used its
own unit-id scheme so the hash didn't matter).  In v2 we
post-process: after `cabal v2-build` registers the unit, append
the lib paths to the `library-dirs:` / `library-dirs-static:` /
`dynamic-library-dirs:` fields, then `ghc-pkg recache`.

Downstream consumers' unit-ids are unaffected — their config hash
records each dep's unit-id, not the dep's .conf content — so the
chain stays plan-nix-consistent end to end.

* v2 builder: use .conf 'name:' field over uid prefix for pkg matching

cabal's OS-prefix patch shortens unit-id prefixes (e.g.
`bytrdr-1.0.4-...` for the `byteorder` package), so parsing the
pkg-name from the uid alone misclassified lib units and failed the
post-install captured-unit check with
`slice captured 0 units for the target package 'byteorder'` even
when the captured uid WAS the right one.

Read the authoritative pkg-name from each unit's `.conf` (which
records the real package name) and fall back to uid parsing only
for bin-only units that don't have a .conf.

* v2 builder: handle sublib pkg-name + drop backpack nix-tools override

Three related cleanups:

* `build-cabal-slice.nix`: prefer `package-name:` over `name:` in
  the captured-unit `.conf` parser.  For sublibs cabal records
  `name: z-<pkg>-z-<sublib>` (z-encoded) but also
  `package-name: <pkg>` — the latter is what the slice's expected
  package check should match against.

* `test/backpack/default.nix`: drop
  `inherit (evalPackages.haskell-nix) nix-tools;`.  The line was
  needed when backpack PR (#2467, 2026-03-17) added a new
  `make-install-plan` flag that wasn't yet in the prebuilt
  `nix-tools-static` tarball.  The tarball has been updated since
  (febad32e4 on 2026-03-24, and subsequently), so the default
  `nix-tools-unchecked` picks up backpack support; the override
  forces an unnecessary from-source build that on aarch64-darwin
  cascades into a v2 slice mismatch on `cabal-install`'s
  `pkg-src.repo.type` (`secure-repo` vs `remote-repo`).

* `test/cabal.project.local`: bump the pinned
  `head.hackage.ghc.haskell.org` `--sha256` to match the current
  index tarball — the previous hash was stale and would fail
  the fixed-output hash check during plan-to-nix.

* v2 builder: accept backpack instantiations in the captured-unit set

The exe slice that consumes an indefinite backpack library ends up
materialising the instantiated form (a `+`-suffixed unit-id, e.g.
`bckpck-0.1.0.0-55b00c8f+Dwq5ijz...`) alongside the exe itself,
because the indefinite consumer's own slice produced no compiled
artifacts to compose in (cabal v2-build for an indefinite library
is "Up to date" and writes no `.conf`).  v1 handles this with a
per-instantiation derivation (`lib/default.nix:369`'s
`components.library.override { inherit instantiations; }`); v2
doesn't yet have a slice-per-instantiation, so the instantiated
unit rides along inside the consuming slice's $out.

Three captured-unit changes:
  * Read pkg-name from cabal's `dist-newstyle/cache/plan.json`
    for bin-only units (no `.conf`).  Falling back to uid parsing
    misclassified OS-prefix-shortened names like `bckpck-...` as
    a package called `bckpck` instead of `backpack`.
  * Allow 0 target-package units (indefinite libraries register
    nothing).
  * Skip `+`-suffixed unit-ids when counting target-package units
    — they're cabal-emitted instantiation byproducts, not the
    slice's own target.  The "no foreign package" invariant
    (everything outside `expectedPackage` ∪ `allowedBuildToolPackages`
    is rejected) still applies and is what catches an accidentally
    rebuilt lib dep.

* v2 builder: error on \`.dwarf\` with migration hint; route DWARF via cabal.project

Mirrors the existing \`.profiled\` treatment.  v1's \`.dwarf\` was an
overlay rebuild with \`enableDWARF = true\`, which would diverge
from plan-nix's UnitId in v2 (the overlay toggles aren't in
plan-nix's hash).  Express DWARF in cabal.project's \`debug-info:\`
instead — the slice itself carries DWARF when that's set, and
\`<slice>.exePath\` gives the debug-info exe directly.

Updated test/cabal-simple-debug to add \`debug-info: 2\` to
cabalProjectLocal and use \`.exePath\` instead of \`.dwarf.exePath\`.

* docs+v2: route DWARF via compilerSelection and debug-info, document the migration

v2 splits v1's monolithic `.dwarf` overlay into two pieces:
  * `debug-info:` in cabal.project for DWARF in the user's source
    (plan-nix records it → matching UnitIds).
  * `compilerSelection` swap to `c.dwarf` for DWARF in the RTS /
    `ghc-internal` (every plan/slice/dummy-ghc derives from the
    same GHC, so UnitIds stay consistent).

  * `docs/dev/debug-info.md` — parallel to docs/dev/profiling.md.
  * `builder/comp-v2-builder.nix` — `.dwarf` now throws with both
    knobs in the migration hint and a pointer to the new doc.
  * `test/cabal-simple-debug/default.nix` — uses both knobs and
    queries `.exePath` instead of `.dwarf.exePath`.

* linux-cross: provide `wrapGhc` for v2 cross-TH

v2 builder's slice cabal v2-build doesn't consume v1's
`setupBuildFlags`, so the cross-TH iserv ghc-options
(`-fexternal-interpreter -pgmi <qemu-wrapper>` etc.) never reached
the cross GHC and slices for packages with TH (e.g. th-orphans,
th-lift) failed with "Couldn't find a target code interpreter.
Try with -fexternal-interpreter" on aarch64-android-prebuilt,
aarch64-multiplatform, and other linux-cross targets.

Mirror `overlays/mingw_w64.nix`'s `wrapGhc`: a derivation that
`--add-flags`-wraps every ghc / ghci binary with the linux-cross
`ghcOptions` (already used by v1 via `setupBuildFlags`).  Cabal
still sees the unwrapped `ghc --info` output (compiler-id, target
platform, capabilities) so UnitId hash inputs are unchanged and
plan-nix's recorded UnitIds keep matching the slice's
computation.  Other GHC binaries (`ghc-pkg`, `hsc2hs`, `runghc`,
`haddock`, …) are symlinked through unchanged.

The v2 comp-v2-builder already consumes
`templateHaskell.wrapGhc` when set (see `sliceGhc`), so wiring
this through is purely additive — windows already had it.

* flake.lock: bump iserv-proxy to 0b8d6f6 (aarch64-android stubs)

* flake.lock: bump iserv-proxy to 3d649af (more aarch64-android stubs)

* iserv-proxy: link statically on Android in cabal.project for v2

v1's iserv-proxy-interpreter.override added `--ghc-option=-optl-static
--ghc-option=-optl-ldl` (and `-optl-no-pie` on aarch32) via
`setupBuildFlags` so the cross-compiled binary is self-contained
— necessary because qemu-user-mode on the build host can't
satisfy Android's dynamic loader (`/system/bin/linker64` /
`/system/bin/linker`).  v2 ignores `setupBuildFlags`, so the
slice's binary came out dynamically linked and qemu refused to
launch it with
`qemu-aarch64: Could not open '/system/bin/linker64'`, breaking
cross-TH for any package that touched iserv on android.

Express the same flags in the iserv-proxy project's
cabalProjectLocal under `if os(android)`.  plan-nix now records
them in the slice's UnitId-relevant configure-args, so the
slice's cabal v2-build picks them up — same outcome as v1.

* iserv-proxy: drop `if os(android)` guard (isAndroid Nix check is enough)

The `final.lib.optionalString final.stdenv.hostPlatform.isAndroid`
already keeps the flags out of the build-platform iserv-proxy
build (different cabalProject' instantiation under pkgsBuildBuild).
Drop the redundant `if os(android)` so the block parses cleanly
— cabal's project-file conditional may not have matched the
android os name we use here, leaving the ghc-options block
inert.

* iserv-proxy: key Android static-link guard off the INNER pkgs

The cabalProjectLocal closure is shared between two
`cabalProject'` instantiations of the iserv-proxy project — one
under `pkgsBuildBuild` (build host iserv-proxy) and one under
`final` (cross-target iserv-proxy-interpreter).  Guarding on
`final.stdenv.hostPlatform.isAndroid` matched both because
`final` is the OUTER cross context (android).  The build-host
iserv-proxy then got `-optl-static` and the link of its shared
library failed with
`requires dynamic R_X86_64_32 reloc against '__TMC_END__' which
may overflow at runtime`.

Use the INNER `pkgs.stdenv.hostPlatform.isAndroid` (the lambda's
own `pkgs` arg) so each instantiation sees its own platform.

The Windows case is fine as-is because it has a cabal-level
`if os(mingw32)` guard inside the project file.

* iserv-proxy: add -debug to Android ghc-options (matches enableDebugRTS)

v1's android override included `enableDebugRTS = true` alongside
`-optl-static -optl-ldl` — the haskell.nix module translates
that to `--ghc-option=-debug`, picking the debug RTS variant.
Without it the statically-linked iserv-proxy-interpreter
segfaulted under qemu-aarch64.  Add `-debug` to the cabal.project
ghc-options block so v2 picks up the same combination.

* iserv-proxy: also pass -optc-fPIC on aarch64 Android (matches v1)

v1's armv6l-linux.nix `addPackageKeys` injects
`--gcc-option=-fPIC` into `setupBuildFlags` for every package on
aarch64 cross builds (work-around for GHC #15275).  For
iserv-proxy specifically this means the cbits get compiled
with -fPIC, which the qemu-emulated static aarch64-android
binary needs at runtime — without it the binary segfaults
inside qemu before iserv even initialises.  v2 ignores
`setupBuildFlags`, so route -fPIC through cabal.project as
`-optc-fPIC` (passes -fPIC to gcc via ghc).

* iserv-proxy: force static/non-shared on Android (match v1 cabal flags)

v1 invokes Setup configure with
`--disable-shared --enable-static --disable-executable-dynamic`
for iserv-proxy on android (via the project's default cabal-pkg
flags), so all iserv artifacts are static — necessary for qemu
to run the binary on the build host without Android's dynamic
loader.  v2's default cabal.project has `shared: True`,
`static: False`, `executable-dynamic: False`; adding the
explicit override for the iserv-proxy package on android gets
the same effect: static lib + static exe.  Avoids the segfault
under qemu that was hitting after `-optl-static` alone left the
linker fighting between a shared lib and static exe.

* iserv-proxy: use cabal executable-static, drop redundant -optl-static

`executable-static: True` is cabal's first-class way to ask for a
static executable; it tells ghc to pass `-static -optl-static` at
the right points, gets dependency static-libs right, and doesn't
interact badly with `-fPIC` the way the standalone `-optl-static`
ghc-option did (which left us with a successfully-linked but
segfaulting binary under qemu-aarch64).

* linux-cross: add -optc-fPIC project-wide on aarch64

v1's armv6l-linux.nix injects `--gcc-option=-fPIC` into every
package's setupBuildFlags on aarch64 (work-around for GHC #15275).
The wrapGhc consumes `linux-cross.ghcOptions` so adding
`-optc-fPIC` there makes the C bits of all cross-compiled
packages get -fPIC just like v1.  Without it the static
aarch64-android iserv binary segfaulted under qemu even with all
the cabal flags matching v1.  Also clean up the per-package
iserv-proxy block (executable-static: True is enough; static/
shared/executable-dynamic flow from there).

* iserv-proxy: drop -debug from android ghc-options

The static aarch64-android iserv binary segfaulted under qemu
before reaching argv parsing — the strace shows it hit the
stack guard page during RTS init.  The debug RTS variant
(`-debug`) does more setup work at startup and pushed stack
usage past qemu's mapped guard.  v1 set …