Build confirmed on haskelseed (511a503), image pushed to Gitea registry,
deployed to Railiance01/coulomb, smoke tests pass (/healthz 200, /Probes 200).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 KiB
id, type, title, domain, repo, status, owner, created, updated, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|
| IRP-WP-0001 | workplan | ihp-railiance-probe — Full Pipeline Validation | stack | ihp-railiance-probe | done | tegwick | 2026-05-02 | 2026-05-07 | 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.nixmust carry the ActualTypes.hs export-list rewrite overlay to prevent.hioverflow (Bug 1).libHSghc-9.10.3-5702.aon 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 -M2gand-j1are mandatory on the 2-CPU/3.8 GB host.
Tasks
T01 — Adopt flake.nix from inter-hub baseline
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:
- Copy
inter-hub/flake.nix→ihp-railiance-probe/flake.nix - Change
appNameto"ihp-railiance-probe" - 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) - Keep the inter-hub-models
configureFlagsandpostUnpackoverlay verbatim — these fix GHC 9.10.3 Bug 1 and are needed regardless of module count - Remove the
inter-hub-liboverlay (it was a workaround that was superseded; confirm it is absent from inter-hub's current flake before copying) - Commit the flake
Exit criteria: nix flake check passes (or --no-build if check is slow).
T02 — Minimal IHP project scaffold
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:
- Verify Determinate Nix +
ihp-neware available on the workstation - If the repo is empty (only README/LICENSE), run:
Alternatively: copy the IHP scaffold from inter-hub and strip everything down to bare bones (single-table schema, no domain modules).
cd /home/worsch/ihp-railiance-probe ihp-new . --name ihp-railiance-probe # or copy scaffold from inter-hub - Confirm
devenv upstarts: app on:8000, Postgres managed by Nix - 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
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:
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:
- Add table definition to
Application/Schema.sql - Run
migrateinsidedevenv shell - Trigger IHP code generation (IHP IDE at
:8001→ Schema tab → regenerate, orbuild-generated-codeinside devenv) - 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
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":
- Add route in
Web/Routes.hs:instance CanRoute HealthController where parseRoute' = do pathPrefix "/healthz" pure HealthAction - Add
HealthControllertoWeb/Types.hs - Implement controller in
Web/Controller/Health.hs:action HealthAction = renderPlain "ok" - Wire into
Web/FrontController.hs - Verify
curl http://localhost:8000/healthz→ok - Commit
Exit criteria: /healthz returns 200 ok in devenv.
T05 — First Hspec integration test
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):
- Add
Test/ProbeControllerSpec.hs: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 - Wire into
Test/Main.hs - Run
testin devenv — test should fail (no/probesroute yet) - Implement minimal
ProbesControllerwithindexaction returning an empty list - Run
testagain — should pass - Commit both the test and the controller together
Exit criteria: test exits 0; test report shows ProbeController spec green.
T06 — Production build on haskelseed
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:
# 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:
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
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):
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:
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
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 /healthzafter 30s initialDelay
Commit the chart.
Exit criteria: helm lint chart/ passes.
T09 — k3s registry configuration on Railiance01
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:
ssh railiance01
sudo cat /etc/rancher/k3s/registries.yaml
# If not present or missing the mirror entry, add:
mirrors:
"92.205.130.254:32166":
endpoint:
- "http://92.205.130.254:32166"
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
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:
# 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:
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
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:
GET /healthz→200 okfrom outside the cluster (via Ingress or NodePort)GET /probes→200(empty list, no crash)- No panic/crash in pod logs within 60 seconds of startup
- Document the verified SHA and timestamp in a
PIPELINE_LOG.mdentry:| 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)
id: IRP-WP-0001-T12
status: todo
priority: low
state_hub_task_id: "0bf9c616-54e6-48f3-ae6d-d91a9f7517c9"
Automate the build → push → deploy pipeline via Gitea Actions:
- Register haskelseed as a Gitea Actions runner:
# On haskelseed: act_runner register --instance http://92.205.130.254:32166 --token <runner-token> --name haskelseed act_runner daemon & - Create
.gitea/workflows/build-and-deploy.yml: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 - 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 |