Files
ihp-railiance-probe/workplans/IRP-WP-0001-pipeline-validation.md
tegwick 6bbfd7430b
Some checks failed
Build and Deploy / build-push-deploy (push) Failing after 11m9s
chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-05-07:
  - IRP-WP-0001-T12: todo → blocked
2026-05-07 12:01:07 +02:00

454 lines
13 KiB
Markdown

---
id: IRP-WP-0001
type: workplan
title: "ihp-railiance-probe — Full Pipeline Validation"
domain: stack
repo: ihp-railiance-probe
status: done
owner: tegwick
created: "2026-05-02"
updated: "2026-05-07"
state_hub_workstream_id: "dce43c1c-1a4c-4b8d-aeb1-7755c9243a38"
---
# ihp-railiance-probe — Full Pipeline Validation
## Goal
Stand up a minimal IHP application that successfully traverses the complete
build-to-production cycle: `nix build` on haskelseed → OCI push to Gitea
registry → Helm deploy to Railiance01 → live HTTP response. The probe carries
one Hspec integration test to prove the test-first loop is closed.
## Background
`inter-hub` exposed two GHC 9.10.3 production-build bugs and an unvalidated
deployment pipeline. This probe exercises the same stack on a trivially small
codebase (one schema table, one controller, one test) so failures are cheap
to diagnose. See `INTENT.md` and `DeploymentBlueprint.md` for full context.
**Key hard-won knowledge going in:**
- `flake.nix` must carry the ActualTypes.hs export-list rewrite overlay to
prevent `.hi` overflow (Bug 1).
- `libHSghc-9.10.3-5702.a` on haskelseed may need a one-time patch if the
full 289 MB archive isn't already in place (Bug 2); check before build.
- `GHCRTS=-A32m -M2g` and `-j1` are mandatory on the 2-CPU/3.8 GB host.
---
## Tasks
### T01 — Adopt flake.nix from inter-hub baseline
```task
id: IRP-WP-0001-T01
status: done
priority: high
state_hub_task_id: "dfaeada0-97fd-454a-99c6-98186419cbc9"
```
Copy `flake.nix` from `inter-hub` as the starting point and strip it down to
the probe's minimal package set:
1. Copy `inter-hub/flake.nix``ihp-railiance-probe/flake.nix`
2. Change `appName` to `"ihp-railiance-probe"`
3. Remove packages not needed: `http-conduit`, `aeson`, `string-conversions`,
`cryptohash-sha256`, `base16-bytestring`, `random-bytestring`, `yaml`,
`network-uri` (add back only as features require them)
4. Keep the inter-hub-models `configureFlags` and `postUnpack` overlay verbatim
— these fix GHC 9.10.3 Bug 1 and are needed regardless of module count
5. Remove the `inter-hub-lib` overlay (it was a workaround that was superseded;
confirm it is absent from inter-hub's current flake before copying)
6. Commit the flake
**Exit criteria:** `nix flake check` passes (or `--no-build` if check is slow).
---
### T02 — Minimal IHP project scaffold
```task
id: IRP-WP-0001-T02
status: done
priority: high
state_hub_task_id: "d982d0a1-ba48-481f-bb2e-dfc0126d36d9"
```
Bootstrap the IHP project skeleton inside the repo:
1. Verify Determinate Nix + `ihp-new` are available on the workstation
2. If the repo is empty (only README/LICENSE), run:
```bash
cd /home/worsch/ihp-railiance-probe
ihp-new . --name ihp-railiance-probe # or copy scaffold from inter-hub
```
Alternatively: copy the IHP scaffold from inter-hub and strip everything
down to bare bones (single-table schema, no domain modules).
3. Confirm `devenv up` starts: app on `:8000`, Postgres managed by Nix
4. Commit baseline scaffold
**Exit criteria:** `devenv up` succeeds; `http://localhost:8000` returns IHP
welcome page or a minimal home view.
---
### T03 — Minimal schema: `probes` table
```task
id: IRP-WP-0001-T03
status: done
priority: high
state_hub_task_id: "675210b9-0e4e-4c08-882a-09ccc19c1854"
```
Define one schema table in `Application/Schema.sql`:
```sql
CREATE TABLE probes (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
```
Steps:
1. Add table definition to `Application/Schema.sql`
2. Run `migrate` inside `devenv shell`
3. Trigger IHP code generation (IHP IDE at `:8001` → Schema tab → regenerate,
or `build-generated-code` inside devenv)
4. Commit migration + generated code
**Exit criteria:** `probes` table exists; generated `Generated/Types.hs` and
`Generated/ActualTypes.hs` are present in `build/`; `devenv up` still compiles.
---
### T04 — Health endpoint controller
```task
id: IRP-WP-0001-T04
status: done
priority: high
state_hub_task_id: "4fba285f-ca9c-4f53-8474-d418ab8b4e1b"
```
Add a minimal `/healthz` route that returns `200 OK` with body `"ok"`:
1. Add route in `Web/Routes.hs`:
```haskell
instance CanRoute HealthController where
parseRoute' = do
pathPrefix "/healthz"
pure HealthAction
```
2. Add `HealthController` to `Web/Types.hs`
3. Implement controller in `Web/Controller/Health.hs`:
```haskell
action HealthAction = renderPlain "ok"
```
4. Wire into `Web/FrontController.hs`
5. Verify `curl http://localhost:8000/healthz` → `ok`
6. Commit
**Exit criteria:** `/healthz` returns `200 ok` in devenv.
---
### T05 — First Hspec integration test
```task
id: IRP-WP-0001-T05
status: done
priority: high
state_hub_task_id: "0555d8ec-3179-4f49-9e80-c1a43d37c7fa"
```
Write the test *before* adding any Probes CRUD (test-first proof):
1. Add `Test/ProbeControllerSpec.hs`:
```haskell
module Test.ProbeControllerSpec where
import Test.Hspec
import IHP.HSpec
spec :: Spec
spec = describe "ProbeController" $ do
it "GET /probes returns 200" $ do
response <- get "/probes"
response `shouldRespondWith` 200
```
2. Wire into `Test/Main.hs`
3. Run `test` in devenv — test should **fail** (no `/probes` route yet)
4. Implement minimal `ProbesController` with `index` action returning an empty list
5. Run `test` again — should pass
6. Commit both the test and the controller together
**Exit criteria:** `test` exits 0; test report shows ProbeController spec green.
---
### T06 — Production build on haskelseed
```task
id: IRP-WP-0001-T06
status: done
priority: high
state_hub_task_id: "6007fdde-3b03-4c7a-ae4e-2106cadd6a56"
```
First `nix build .#docker` on haskelseed for the probe:
**Pre-build checklist:**
```bash
# 1. Verify libHSghc-9.10.3-5702.a is full (should be 289,295,782 bytes)
wc -c /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
# If ~287 MB, apply archive patch before proceeding (see HaskellVibePrimer.md §Bug 2)
# 2. Ensure source is on haskelseed
scp flake.nix + source tree → root@192.168.178.135:/root/ihp-railiance-probe/
# or: git push + git pull on haskelseed
```
Build steps:
```bash
sshpass -p 'hcs26!x' ssh root@192.168.178.135 \
'cd /root/ihp-railiance-probe && nix build .#docker --log-format raw \
> /tmp/probe-build01.log 2>&1 &'
```
Monitor with tail; expect 30-50 min on first build (no cache).
**Exit criteria:** `result` symlink present on haskelseed; `nix log` shows no errors.
---
### T07 — Push OCI image to Gitea registry
```task
id: IRP-WP-0001-T07
status: done
priority: medium
state_hub_task_id: "24b892fa-2a81-4606-b7a8-20e493c89441"
```
Push the built image to the Gitea container registry.
**Note:** Gitea's registry token realm is misconfigured — it points to
`gitea.coulomb.social:80` but Gitea runs on port 32166. Pre-fetch the token
manually and pass it with `--dest-registry-token` to bypass the broken token
dance (no `iptables` on haskelseed's Alpine to redirect ports):
```bash
sshpass -p 'hcs26!x' ssh root@192.168.178.135 bash <<'EOF'
cd /root/ihp-railiance-probe
SHA=$(git rev-parse --short HEAD)
SKOPEO=/nix/store/fwdagky9lfsyrgzxiq14zijcziazfdsn-skopeo-1.22.2/bin/skopeo
TOKEN=$(curl -s \
"http://92.205.130.254:32166/v2/token?service=container_registry&scope=repository:coulomb/ihp-railiance-probe:push,pull" \
-u 'tegwick:<GITEA_API_KEY>' | awk -F'"' '/token/{print $4}')
$SKOPEO copy --insecure-policy --dest-tls-verify=false \
--dest-registry-token "$TOKEN" \
docker-archive:result \
docker://92.205.130.254:32166/coulomb/ihp-railiance-probe:$SHA
EOF
```
Verify via the registry API:
```bash
TOKEN=$(curl -s "http://92.205.130.254:32166/v2/token?service=container_registry&scope=repository:coulomb/ihp-railiance-probe:pull" \
-u 'tegwick:<GITEA_API_KEY>' | awk -F'"' '/token/{print $4}')
curl -s -H "Authorization: Bearer $TOKEN" \
"http://92.205.130.254:32166/v2/coulomb/ihp-railiance-probe/tags/list"
```
**Exit criteria:** `skopeo inspect` succeeds; image visible in Gitea Packages UI.
---
### T08 — Helm chart
```task
id: IRP-WP-0001-T08
status: done
priority: medium
state_hub_task_id: "06eb278b-8ff6-4123-a2e3-856dc44ed275"
```
Create a minimal Helm chart in `chart/`:
```
chart/
Chart.yaml # name: ihp-railiance-probe, version: 0.1.0
values.yaml # image.repository, image.tag, env vars
templates/
deployment.yaml # single replica, port 8000, envFrom secretRef
service.yaml # ClusterIP, port 80 → 8000
ingress.yaml # Traefik IngressRoute or standard Ingress
secret.yaml # IHP_SESSION_SECRET, DATABASE_URL, IHP_BASEURL
```
Key `deployment.yaml` notes:
- Image: `{{ .Values.image.repository }}:{{ .Values.image.tag }}`
- Repository default: `92.205.130.254:32166/coulomb/ihp-railiance-probe`
- `imagePullPolicy: Always`
- Resource limits: `memory: 256Mi`, `cpu: 200m` (probe is small)
- Liveness probe: `GET /healthz` after 30s initialDelay
Commit the chart.
**Exit criteria:** `helm lint chart/` passes.
---
### T09 — k3s registry configuration on Railiance01
```task
id: IRP-WP-0001-T09
status: done
priority: medium
state_hub_task_id: "97f50f12-3511-408f-a049-9300241344b8"
```
Configure k3s to pull from the HTTP (non-TLS) Gitea registry:
```bash
ssh railiance01
sudo cat /etc/rancher/k3s/registries.yaml
# If not present or missing the mirror entry, add:
```
```yaml
mirrors:
"92.205.130.254:32166":
endpoint:
- "http://92.205.130.254:32166"
```
```bash
sudo systemctl restart k3s
```
Verify: `sudo k3s crictl pull 92.205.130.254:32166/coulomb/ihp-railiance-probe:<SHA>`
**Exit criteria:** image pulls successfully on Railiance01.
---
### T10 — Deploy to Railiance01
```task
id: IRP-WP-0001-T10
status: done
priority: medium
state_hub_task_id: "7c79c843-5497-4d88-985d-a915145691cd"
```
Deploy the probe to the `coulomb` namespace:
```bash
# Create namespace if not present
kubectl --context railiance01 create namespace coulomb --dry-run=client -o yaml | kubectl apply -f -
# Create/update secret
kubectl --context railiance01 -n coulomb create secret generic ihp-railiance-probe-env \
--from-literal=IHP_SESSION_SECRET="$(openssl rand -base64 32)" \
--from-literal=DATABASE_URL="postgresql://..." \
--from-literal=IHP_BASEURL="https://probe.coulomb.example" \
--dry-run=client -o yaml | kubectl apply -f -
# Deploy
helm --kube-context railiance01 upgrade --install ihp-railiance-probe ./chart \
--namespace coulomb \
--set image.tag=<SHA>
```
**Exit criteria:**
```bash
kubectl -n coulomb get pods | grep ihp-railiance-probe # Running
kubectl -n coulomb logs deploy/ihp-railiance-probe | tail -5 # IHP startup
curl http://<cluster-ip>/healthz # ok
```
---
### T11 — End-to-end smoke test
```task
id: IRP-WP-0001-T11
status: done
priority: medium
state_hub_task_id: "cb2b1ee7-6c54-48b5-8cd8-b4fcb8fa07ab"
```
Verify the full pipeline produced a live application:
1. `GET /healthz` → `200 ok` from outside the cluster (via Ingress or NodePort)
2. `GET /probes` → `200` (empty list, no crash)
3. No panic/crash in pod logs within 60 seconds of startup
4. Document the verified SHA and timestamp in a `PIPELINE_LOG.md` entry:
```
| 2026-05-02 | <SHA> | Build: haskelseed | Push: 92.205.130.254:32166 | Deploy: Railiance01 | Smoke: PASS |
```
**Exit criteria:** All three HTTP checks pass; log entry committed.
---
### T12 — Gitea Actions CI (optional, Phase 2)
```task
id: IRP-WP-0001-T12
status: blocked
priority: low
state_hub_task_id: "0bf9c616-54e6-48f3-ae6d-d91a9f7517c9"
```
Automate the build → push → deploy pipeline via Gitea Actions:
1. Register haskelseed as a Gitea Actions runner:
```bash
# On haskelseed:
act_runner register --instance http://92.205.130.254:32166 --token <runner-token> --name haskelseed
act_runner daemon &
```
2. Create `.gitea/workflows/build-and-deploy.yml`:
```yaml
on: [push]
jobs:
build:
runs-on: haskelseed
steps:
- uses: actions/checkout@v3
- run: nix build .#docker --log-format raw
- run: |
SHA=$(git rev-parse --short HEAD)
skopeo copy docker-archive:result \
docker://92.205.130.254:32166/coulomb/ihp-railiance-probe:$SHA
- run: |
SHA=$(git rev-parse --short HEAD)
helm upgrade --install ihp-railiance-probe ./chart \
--namespace coulomb --set image.tag=$SHA
```
3. Trigger a push; verify pipeline runs end-to-end
**Exit criteria:** CI pipeline runs without manual intervention on each push to `main`.
---
## Exit Criteria Summary
| Task | Check | Status |
|------|-------|--------|
| T01 | flake.nix with overlay from inter-hub | done |
| T02 | `devenv up` → IHP welcome page | done |
| T03 | `probes` table in DB; code-gen passes | done |
| T04 | `/healthz` returns `200 ok` | done |
| T05 | Hspec `test` exits 0 | done |
| T06 | `nix build .#docker` on haskelseed succeeds | done |
| T07 | Image visible in Gitea registry | done |
| T08 | `helm lint chart/` passes | done |
| T09 | k3s can pull from HTTP registry | done |
| T10 | Pod Running on Railiance01 | done |
| T11 | Smoke tests pass; log entry committed | done |
| T12 | CI pipeline automated (optional) | todo |