Files
ihp-railiance-probe/workplans/IRP-WP-0001-pipeline-validation.md
tegwick 82c2676a71 chore: mark T06-T11 done; pipeline validated end-to-end
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>
2026-05-07 04:07:22 +02:00

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.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

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.nixihp-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

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:
    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

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:

  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

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:
    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:
    action HealthAction = renderPlain "ok"
    
  4. Wire into Web/FrontController.hs
  5. Verify curl http://localhost:8000/healthzok
  6. 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):

  1. 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
    
  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

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 /healthz after 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:

  1. GET /healthz200 ok from outside the cluster (via Ingress or NodePort)
  2. GET /probes200 (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)

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:

  1. 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 &
    
  2. 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
    
  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