generated from coulomb/repo-seed
This seems to be our first runnable version on railiance01
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
This commit is contained in:
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"sessionId":"7a72372c-e488-456c-baba-1e60d38649cf","pid":9468,"procStart":"127351","acquiredAt":1777404376811}
|
||||||
426
HaskellVibePrimer.md
Normal file
426
HaskellVibePrimer.md
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
# Haskell Vibe Primer
|
||||||
|
## Hard-won lessons for coding agents working on inter-hub and IHP projects
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick orientation
|
||||||
|
|
||||||
|
Inter-hub is an IHP (Integrated Haskell Platform) v1.5 application on GHC 9.10.3, managed via Nix/devenv. It has two distinct compilation modes with fundamentally different behaviour:
|
||||||
|
|
||||||
|
| Mode | Command | Compiler | Output | Crashes? |
|
||||||
|
|------|---------|----------|--------|----------|
|
||||||
|
| Dev | `devenv up` → ghcid | GHCi byte-code | In-memory | No (byte-code avoids static linker) |
|
||||||
|
| Production | `nix build .#docker` | Native (cabal) | OCI image | Yes, if archive bug present |
|
||||||
|
|
||||||
|
A build that works in `devenv up` does **not** guarantee `nix build .#docker` will succeed. The production build hits code paths that ghcid never reaches.
|
||||||
|
|
||||||
|
The build host for production is **haskelseed** (192.168.178.135, root/hcs26!x), not CoulombCore. Do not try to run `nix build` on CoulombCore.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1 — Known GHC 9.10.3 bugs in this project
|
||||||
|
|
||||||
|
### Bug 1: `module M` re-export causes `.hi` overflow (FIXED in flake.nix)
|
||||||
|
|
||||||
|
**Symptom:**
|
||||||
|
```
|
||||||
|
Data.Binary.Get.runGet at position ~287000000: not enough bytes
|
||||||
|
```
|
||||||
|
Crash occurs while GHC reads a `.hi` (interface) file.
|
||||||
|
|
||||||
|
**Cause:** IHP generates a hub module `Generated/ActualTypes.hs` that originally used:
|
||||||
|
```haskell
|
||||||
|
module Generated.ActualTypes
|
||||||
|
( module Generated.Foo
|
||||||
|
, module Generated.Bar
|
||||||
|
... -- 61 sub-modules
|
||||||
|
) where
|
||||||
|
import Generated.Foo
|
||||||
|
import Generated.Bar
|
||||||
|
```
|
||||||
|
|
||||||
|
The `module M` re-export syntax causes GHC to **embed the full exported interface of every sub-module verbatim** into `ActualTypes.hi`. With 61 sub-modules and thousands of typeclass instances, the resulting `.hi` reaches ~287 MB — exceeding GHC 9.10.3's `Data.Binary.Get` 274 MB deserialization limit.
|
||||||
|
|
||||||
|
**Fix (active in `flake.nix`):** The `inter-hub-models` postUnpack overlay rewrites the export list to explicit `T(..)` per-type exports:
|
||||||
|
```haskell
|
||||||
|
module Generated.ActualTypes
|
||||||
|
( Foo(..)
|
||||||
|
, FooId(..)
|
||||||
|
, Bar(..)
|
||||||
|
, ...
|
||||||
|
) where
|
||||||
|
```
|
||||||
|
Explicit re-exports store only compact name-references in `.hi` (not embedded sub-interfaces). `ActualTypes.hi` drops from ~287 MB to ~51 KB.
|
||||||
|
|
||||||
|
**Rule: NEVER use `module M` re-export syntax for generated hub modules with many sub-modules.** Always use explicit `T(..)` exports.
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- BAD: embeds sub-interfaces into hub .hi
|
||||||
|
module MyHub (module Sub1, module Sub2) where
|
||||||
|
|
||||||
|
-- GOOD: stores compact references
|
||||||
|
module MyHub (Foo(..), Bar, baz) where
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Bug 2: Truncated `libHSghc-9.10.3-5702.a` in Nix store (FIXED on haskelseed)
|
||||||
|
|
||||||
|
**Symptom:**
|
||||||
|
```
|
||||||
|
panic! (the 'impossible' happened)
|
||||||
|
GHC version 9.10.3:
|
||||||
|
Data.Binary.Get.runGet at position 287686318: not enough bytes
|
||||||
|
```
|
||||||
|
Crash occurs **after** `[477 of 477] Compiling Generated.WidgetVersionInclude` — all modules compiled, crash in post-compilation step.
|
||||||
|
|
||||||
|
**Cause:** The Nix-provisioned static archive for the GHC compiler-as-library is truncated:
|
||||||
|
- Truncated: `ffg3yf2.../lib/.../ghc-9.10.3-5702/libHSghc-9.10.3-5702.a` — 287,768,576 bytes
|
||||||
|
- Full archive: `ffg3yf2.../ghc-9.10.3-partial/lib/.../ghc-9.10.3-5702/libHSghc-9.10.3-5702.a` — 289,295,782 bytes
|
||||||
|
|
||||||
|
The last AR entry (`Expr.o`) claims 517,544 bytes but only 82,258 are present. GHC's `readAr` (via `Data.Binary.Get.runGet`) panics when it tries to read the full entry.
|
||||||
|
|
||||||
|
Why GHC reads this archive at all: The global `ghc-with-packages` package db is searched during the build (cabal configure does not pass `--package-db=clear`). That db registers the `ghc` compiler-as-library package with `library-dirs` pointing to the directory containing the truncated archive symlink. GHC loads this archive during internal post-compilation processing — even when the package has **no Template Haskell**.
|
||||||
|
|
||||||
|
**What does NOT fix it:**
|
||||||
|
- `-fomit-interface-pragmas` — reduces `.hi` size, unrelated to archive loading
|
||||||
|
- `--disable-shared` — affects output type, not input archive reading
|
||||||
|
- `-fexternal-interpreter -pgmi ghc-iserv-dyn` — routes TH to external process, but inter-hub-models has **zero TH**; the archive read is from a different code path
|
||||||
|
|
||||||
|
**Fix applied on haskelseed (2026-05-02):**
|
||||||
|
```bash
|
||||||
|
TRUNCATED="/nix/store/ffg3yf2ypnbz3hc31y7nglrkihz0if01-ghc-9.10.3/lib/ghc-9.10.3/lib/x86_64-linux-ghc-9.10.3/ghc-9.10.3-5702/libHSghc-9.10.3-5702.a"
|
||||||
|
FULL="/nix/store/ffg3yf2ypnbz3hc31y7nglrkihz0if01-ghc-9.10.3/ghc-9.10.3-partial/lib/ghc-9.10.3/lib/x86_64-linux-ghc-9.10.3/ghc-9.10.3-5702/libHSghc-9.10.3-5702.a"
|
||||||
|
chmod u+w "$(dirname "$TRUNCATED")"
|
||||||
|
cp "$FULL" "$TRUNCATED"
|
||||||
|
chmod a-w "$TRUNCATED" "$(dirname "$TRUNCATED")"
|
||||||
|
```
|
||||||
|
|
||||||
|
**If the fix is lost** (flake lock update changing GHC version):
|
||||||
|
```bash
|
||||||
|
# Check archive size before building — should be ~289 MB
|
||||||
|
wc -c /nix/store/HASH-ghc-9.10.3/lib/ghc-9.10.3/lib/x86_64-linux-ghc-9.10.3/ghc-9.10.3-5702/libHSghc-9.10.3-5702.a
|
||||||
|
# If ~287 MB, look for ghc-9.10.3-partial/ in same store path and apply patch
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Bug 3: `--disable-shared` causes missing `.dyn_hi` files (FIXED in flake.nix)
|
||||||
|
|
||||||
|
If `inter-hub-models` is built with `--disable-shared`, it produces only `.hi` files but no `.dyn_hi` (dynamic interface files). The dependent `inter-hub-lib` package (built with `--enable-shared` by default in NixPkgs) requires `.dyn_hi` from its dependencies and fails with:
|
||||||
|
```
|
||||||
|
Failed to load dynamic interface file for Generated.Foo:
|
||||||
|
.../Generated/Foo.dyn_hi: does not exist
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not add `--disable-shared` to the inter-hub-models configureFlags unless you also suppress `--enable-shared` in inter-hub-lib (and its transitive dependents). The archive fix (Bug 2) is the correct solution; `--disable-shared` was a failed hypothesis.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2 — IHP compilation architecture
|
||||||
|
|
||||||
|
### Compilation layers (from CLAUDE.md)
|
||||||
|
|
||||||
|
```
|
||||||
|
Layer 1 — stable core (compile once, expensive to change):
|
||||||
|
build/Generated/Types.hs IHP auto-generated from Schema.sql
|
||||||
|
Web/Types.hs controller/action type definitions
|
||||||
|
|
||||||
|
Layer 2 — helpers (only touch if Layer 1 is clean):
|
||||||
|
Application/Helper/*.hs 12 helper modules
|
||||||
|
|
||||||
|
Layer 3 — working surface (most errors live here):
|
||||||
|
Web/Controller/*.hs 59 controllers
|
||||||
|
Web/View/**/*.hs 120 views
|
||||||
|
|
||||||
|
Layer 4 — wiring (fix last):
|
||||||
|
Web/FrontController.hs
|
||||||
|
Web/Routes.hs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix errors bottom-up, one module at a time.** Never touch Layer 1 during an error-fix loop. A single change to `Web/Types.hs` invalidates all 59 controllers and 120 views simultaneously — equivalent to a full rebuild.
|
||||||
|
|
||||||
|
### Package split
|
||||||
|
|
||||||
|
The project compiles as two separate Cabal packages:
|
||||||
|
|
||||||
|
| Package | Source | Contains |
|
||||||
|
|---------|--------|----------|
|
||||||
|
| `inter-hub-models` | `inter-hub-models-src/build/Generated/` | All IHP-generated types, ~477 modules |
|
||||||
|
| `inter-hub-lib` | `inter-hub-lib-src/` | Application code: `Application/`, `Config/`, `Web/` |
|
||||||
|
|
||||||
|
Critical: **`inter-hub-lib-src` has no `build/` directory.** Generated modules come from the `inter-hub-models` package, not from the lib source tree. Any script that assumes lib-src contains `build/Generated/` will fail. To access generated file content from within inter-hub-lib's build scripts, find inter-hub-models-src dynamically:
|
||||||
|
```bash
|
||||||
|
_models_src=$(find /nix/store -maxdepth 1 -name "*-inter-hub-models-src" ! -name "*.drv" | head -1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generated code — what changes when schema changes
|
||||||
|
|
||||||
|
When `Application/Schema.sql` changes and you regenerate via the IHP IDE:
|
||||||
|
- `build/Generated/Types.hs` — one-liner re-export hub (regenerated, do not edit)
|
||||||
|
- `build/Generated/ActualTypes.hs` — type hub (regenerated, patched by postUnpack overlay)
|
||||||
|
- `build/Generated/Foo.hs` — one per table, data type + instances
|
||||||
|
- `build/Generated/FooInclude.hs` — `type instance Include` for eager loading
|
||||||
|
- `build/Generated/ActualTypes/Foo.hs` — sub-module used by ActualTypes hub
|
||||||
|
|
||||||
|
After any schema change, check whether the postUnpack in `flake.nix` still correctly rewrites `Generated.ActualTypes` — run `nix build --keep-failed` and inspect the rewritten file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3 — Nix/devenv specifics
|
||||||
|
|
||||||
|
### How the Nix overlay works
|
||||||
|
|
||||||
|
The `flake.nix` has an `overlays = lib.mkAfter [...]` block in `devenv.shells.default` that overrides the Haskell package set. It intercepts derivations by `pname`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
mkDerivation = args:
|
||||||
|
let drv = hprev.mkDerivation args;
|
||||||
|
in if (args.pname or "") == "inter-hub-models"
|
||||||
|
then drv.overrideAttrs (old: { ... })
|
||||||
|
else if (args.pname or "") == "inter-hub-lib"
|
||||||
|
then drv.overrideAttrs (old: { ... })
|
||||||
|
else drv;
|
||||||
|
```
|
||||||
|
|
||||||
|
`postUnpack` runs after the source is unpacked but before configure. `postConfigure` runs after `setup configure`. Both run inside the Nix sandbox.
|
||||||
|
|
||||||
|
### Environment variables in Nix builds
|
||||||
|
|
||||||
|
- `$sourceRoot` — relative path to unpacked source (e.g., `inter-hub-models-src`)
|
||||||
|
- `$TMPDIR` — build-specific temp directory (e.g., `/nix/var/nix/builds/nix-PID-RND/`)
|
||||||
|
- `$NIX_BUILD_CORES` — parallelism (8 on haskelseed); NixPkgs Haskell builder passes `--ghc-option=-j$NIX_BUILD_CORES` by default
|
||||||
|
- `$NIX_BUILD_TOP` — may not be set; prefer `$TMPDIR`
|
||||||
|
|
||||||
|
### Configure flags ordering matters
|
||||||
|
|
||||||
|
NixPkgs Haskell builder prepends its own configureFlags before yours. Your overlay's flags are **appended** via `(old.configureFlags or []) ++ [ your-flags ]`. For boolean flags (`--enable-X` / `--disable-X`), the last one wins. For `--ghc-option=-jN`, all are passed and GHC uses the last one.
|
||||||
|
|
||||||
|
Example of NixPkgs flags you must know about:
|
||||||
|
```
|
||||||
|
--enable-shared
|
||||||
|
--enable-split-sections
|
||||||
|
--ghc-option=-j8 (from NIX_BUILD_CORES)
|
||||||
|
```
|
||||||
|
|
||||||
|
To override: append `--disable-shared`, `--disable-split-sections`, `--ghc-option=-j1` in your configureFlags. But beware: disabling shared breaks `.dyn_hi` for downstream packages.
|
||||||
|
|
||||||
|
### The `ghc-with-packages` symlink chain
|
||||||
|
|
||||||
|
The GHC wrapper used in builds (`ghc-with-packages`) exposes packages from both the wrapped GHC derivation and registered Haskell packages. Its `lib/` directory contains symlinks back into the unwrapped GHC derivation. When patching Nix store archives, verify the symlink chain resolves correctly:
|
||||||
|
```bash
|
||||||
|
readlink -f /nix/store/WRAP-ghc-9.10.3-with-packages/lib/.../libHSghc.a
|
||||||
|
wc -c /nix/store/WRAP-ghc-9.10.3-with-packages/lib/.../libHSghc.a
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diagnosing `nix build` failures efficiently
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get full build log for a specific derivation
|
||||||
|
nix log /nix/store/HASH-inter-hub-models-0.1.0.drv
|
||||||
|
|
||||||
|
# What derivations would be built (dry run)
|
||||||
|
nix build .#docker --dry-run
|
||||||
|
|
||||||
|
# Keep failed build artifacts for inspection
|
||||||
|
nix build .#docker --keep-failed
|
||||||
|
|
||||||
|
# Check if a store path exists (was the drv already built?)
|
||||||
|
ls /nix/store/EXPECTED-OUTPUT-HASH 2>/dev/null && echo "cached" || echo "not built"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4 — Haskell type system patterns
|
||||||
|
|
||||||
|
### Interface files (`.hi` and `.dyn_hi`)
|
||||||
|
|
||||||
|
GHC produces two kinds of interface files:
|
||||||
|
- `.hi` — static interface, for non-shared compilations
|
||||||
|
- `.dyn_hi` — dynamic interface, required when compiling with `--enable-shared` / `-dynamic-too`
|
||||||
|
|
||||||
|
A package compiled with `--disable-shared` produces `.hi` only. Any package depending on it that was compiled with `--enable-shared` will fail to load the `.dyn_hi`. Always maintain consistent shared/static settings across a package dependency chain.
|
||||||
|
|
||||||
|
### Type families vs Template Haskell
|
||||||
|
|
||||||
|
`type instance` declarations (like `Include "widgetId" ...`) are **type-level computations processed entirely by GHC during type-checking**. They are NOT Template Haskell, do NOT require the TH runtime, and do NOT trigger archive loading via the internal linker.
|
||||||
|
|
||||||
|
Template Haskell requires the TH evaluator (internal or external interpreter) only for modules with:
|
||||||
|
- `{-# LANGUAGE TemplateHaskell #-}` pragma
|
||||||
|
- Splice expressions `$(...)` or quasi-quotes `[| ... |]`
|
||||||
|
- `deriveGeneric`, `makeLenses`, etc. (lens/generics libraries)
|
||||||
|
|
||||||
|
If a module has none of these, `-fexternal-interpreter` has no meaningful effect on it.
|
||||||
|
|
||||||
|
### `module M` re-export vs explicit exports
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- EMBEDS sub-interface into .hi (dangerous for large hubs):
|
||||||
|
module Hub (module Sub) where
|
||||||
|
import Sub
|
||||||
|
|
||||||
|
-- Stores compact name-reference in .hi (correct for large hubs):
|
||||||
|
module Hub (Foo(..), Bar, baz) where
|
||||||
|
import Sub (Foo(..), Bar, baz)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `module M` form is fine for small modules (< 10 re-exported sub-modules with modest interfaces). It becomes a GHC 9.10.3 bomb for generated hubs with 50+ sub-modules full of typeclass instances.
|
||||||
|
|
||||||
|
### AR archive format and `readAr`
|
||||||
|
|
||||||
|
GHC's internal static linker uses `Data.Binary.Get.runGet` to read AR archives (`.a` files). Unlike `ar t` (which only reads headers), `readAr` reads **all entry content**. A truncated archive that passes `ar t` will still panic `readAr`. If you see:
|
||||||
|
|
||||||
|
```
|
||||||
|
panic! (the 'impossible' happened)
|
||||||
|
Data.Binary.Get.runGet at position N: not enough bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `ar tv /path/to/suspect.a | tail -5` — if the last entry shows a size, check whether `wc -c /path/to/suspect.a` is substantially smaller than expected.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 5 — Build compile tools and discipline
|
||||||
|
|
||||||
|
### Compile check scripts
|
||||||
|
|
||||||
|
Inside `devenv shell`:
|
||||||
|
```bash
|
||||||
|
scripts/compile-check # full build via ghcid (all layers), errors → /tmp/ihub-compile-errors.txt
|
||||||
|
scripts/compile-check --bg # background-friendly (no colour/title escape codes)
|
||||||
|
scripts/compile-check-core # Layer 1+2 only — verify clean base
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error-fix discipline
|
||||||
|
|
||||||
|
1. Fix **bottom-up**: Layer 1 → 2 → 3 → 4
|
||||||
|
2. Fix **one module at a time** — wait for ghcid to reload before touching the next module
|
||||||
|
3. **Never change Layer 1 (Web/Types.hs, Generated/Types.hs) while debugging Layer 3 errors** — it restarts the entire recompile
|
||||||
|
4. `Generated/Types.hs` is auto-generated from `Application/Schema.sql`. Edit the schema in IHP IDE, not the generated file
|
||||||
|
|
||||||
|
### Reading GHC error messages
|
||||||
|
|
||||||
|
GHC errors reference **source locations**, not compiled output. When you see:
|
||||||
|
```
|
||||||
|
Web/Controller/Foo.hs:42:5: error:
|
||||||
|
• Couldn't match expected type 'UUID' with actual type 'Text'
|
||||||
|
```
|
||||||
|
|
||||||
|
The fix is in `Web/Controller/Foo.hs:42`, not in any generated file.
|
||||||
|
|
||||||
|
When you see:
|
||||||
|
```
|
||||||
|
Failed to load interface for 'Generated.Foo'
|
||||||
|
```
|
||||||
|
Check that `Generated.Foo` is in the `inter-hub-models` package and that models compiled successfully first.
|
||||||
|
|
||||||
|
### Parallel compilation flag (`-j`)
|
||||||
|
|
||||||
|
GHC's `-j` flag controls **parallel module compilation** within a single package. On haskelseed (2 CPU, ~3.8 GiB RAM), NixPkgs sets `-j8` (from `NIX_BUILD_CORES=8`). The overlay overrides to `-j1` for the models package to prevent OOM on the constrained host. Do not increase this without verifying available memory:
|
||||||
|
```bash
|
||||||
|
free -m # on haskelseed
|
||||||
|
```
|
||||||
|
|
||||||
|
The env var `GHCRTS = "-A32m -M2g"` caps the GHC heap at 2 GiB and sets minor GC allocation to 32 MB. These are set in `devenv.shells.default.env`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 6 — Guardrails: expensive mistakes to avoid
|
||||||
|
|
||||||
|
### NEVER do these
|
||||||
|
|
||||||
|
| Action | Why it's expensive |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Edit `build/Generated/Types.hs` directly | Regenerated on next schema sync; change is lost. Also, editing it changes Layer 1 and invalidates all 477 modules |
|
||||||
|
| Add `module M` re-export syntax to a new generated hub module | Embeds sub-interfaces → `.hi` overflow → GHC crash |
|
||||||
|
| Change `Web/Types.hs` or `Web/Types.hs` during a Layer 3 error loop | Restarts compile of all 59 controllers + 120 views |
|
||||||
|
| Add `--disable-shared` to models without removing it from lib | Missing `.dyn_hi` crash in every downstream package |
|
||||||
|
| Hardcode Nix store hashes in postUnpack scripts | Hash changes with every schema change; use `find /nix/store` instead |
|
||||||
|
| Run `nix build .#docker` on CoulombCore | Insufficient RAM (< 4 GiB), will OOM during GHC compilation |
|
||||||
|
| Trust `ar t` to validate an archive | `ar t` reads only headers; GHC reads content. Use `wc -c` and compare to expected size |
|
||||||
|
| Assume `devenv up` success = `nix build` success | Different compilation modes; static linker issues only surface in `nix build` |
|
||||||
|
|
||||||
|
### Before modifying `flake.nix` overlays
|
||||||
|
|
||||||
|
1. **Check what derivation hash the current flake produces**: `nix build .#docker --dry-run`
|
||||||
|
2. **Understand layer dependencies**: changing inter-hub-models configureFlags invalidates models AND all downstream (lib, binaries, docker image)
|
||||||
|
3. **Test postUnpack scripts locally first**: simulate with `bash -n yourscript.sh` and run `awk` commands against sample files
|
||||||
|
4. **Verify `.dyn_hi` will be produced** if `--enable-shared` is set (which is the NixPkgs default)
|
||||||
|
|
||||||
|
### Diagnosing a new crash before trying fixes
|
||||||
|
|
||||||
|
1. Get the full log: `nix log /nix/store/HASH-inter-hub-models-0.1.0.drv`
|
||||||
|
2. Find the crash position: `Data.Binary.Get.runGet at position N`
|
||||||
|
3. Determine which file is being read at that position (check file sizes of `.hi`, `.a`, `.conf` etc.)
|
||||||
|
4. Check `ar tv suspect.a | tail -5` to see if the last AR entry header is valid
|
||||||
|
5. Only then propose a fix — flag-based fixes almost never work for archive truncation issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 7 — IHP-specific Haskell patterns
|
||||||
|
|
||||||
|
### IHP record access
|
||||||
|
|
||||||
|
IHP generates `HasField` instances. Access fields with:
|
||||||
|
```haskell
|
||||||
|
record.fieldName -- accessor (uses OverloadedRecordDot or get)
|
||||||
|
record |> set #fieldName value -- functional update
|
||||||
|
```
|
||||||
|
|
||||||
|
### IHP type family: `Include`
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- Generated/WidgetVersionInclude.hs (typical):
|
||||||
|
type instance Include "widgetId" (WidgetVersion' widgetId) =
|
||||||
|
WidgetVersion' (GetModelById widgetId)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is pure type-level. `Include` is a closed type family that fills in the concrete type for an association when you do eager loading with `fetchRelated`. It is NOT Template Haskell.
|
||||||
|
|
||||||
|
### IHP controller pattern
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- Each action is a function returning an IO Response (via implicit context)
|
||||||
|
action ShowWidgetAction { widgetId } = do
|
||||||
|
widget <- fetch widgetId
|
||||||
|
render ShowView { .. }
|
||||||
|
```
|
||||||
|
|
||||||
|
Controller type errors almost always mean a mismatch in `Web/Types.hs` — the action type declared there must match the implementation.
|
||||||
|
|
||||||
|
### Schema → Generated code cycle
|
||||||
|
|
||||||
|
```
|
||||||
|
Edit Application/Schema.sql
|
||||||
|
→ IHP IDE at localhost:8001 generates build/Generated/
|
||||||
|
→ Regenerated: Types.hs, ActualTypes.hs, Foo.hs, FooInclude.hs
|
||||||
|
→ Also regenerates Web/Types.hs (action types) and Web/Routes.hs
|
||||||
|
→ Run compile-check to verify
|
||||||
|
```
|
||||||
|
|
||||||
|
After schema changes, always verify the postUnpack overlay in `flake.nix` still produces valid Haskell. The `_types` / `_exports` awk scripts in postUnpack depend on the generated file structure remaining stable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick reference card
|
||||||
|
|
||||||
|
```
|
||||||
|
# Build status checks (on haskelseed)
|
||||||
|
nix build .#docker --dry-run # what would be built?
|
||||||
|
nix log /nix/store/HASH-inter-hub-models-0.1.0.drv # full build log
|
||||||
|
wc -c /nix/store/ffg3yf2.../libHSghc-9.10.3-5702.a # verify archive size (must be ~289 MB)
|
||||||
|
|
||||||
|
# If archive is truncated (287 MB), apply patch:
|
||||||
|
# Full archive is at same derivation's ghc-9.10.3-partial/ subdirectory
|
||||||
|
# See project_ghc_build_fix.md in Claude memory for exact commands
|
||||||
|
|
||||||
|
# Compilation layers (in devenv shell)
|
||||||
|
scripts/compile-check-core # Layer 1+2 only
|
||||||
|
scripts/compile-check # Full build, errors → /tmp/ihub-compile-errors.txt
|
||||||
|
|
||||||
|
# Schema regeneration
|
||||||
|
# Use IHP IDE at localhost:8001, then run compile-check
|
||||||
|
|
||||||
|
# Production build → push → deploy
|
||||||
|
nix build .#docker
|
||||||
|
skopeo copy docker-archive:result docker://92.205.130.254:32166/coulomb/inter-hub:$(git rev-parse --short HEAD)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user