generated from coulomb/repo-seed
docs: add INTENT, DeploymentBlueprint, and pipeline workplan
INTENT.md captures why the probe exists (validate build-to-deploy before inter-hub attempts production again) and its success criteria. DeploymentBlueprint.md is a textual C4 deployment diagram covering all four nodes: workstation, haskelseed, CoulombCore/Gitea, and Railiance01/k3s, plus the full artifact flow and known infrastructure constraints. IRP-WP-0001 is a 12-task workplan: flake bootstrap → minimal IHP scaffold → schema → health endpoint → Hspec test → production build → push → Helm chart → k3s registry config → deploy → smoke test → optional CI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
425
workplans/IRP-WP-0001-pipeline-validation.md
Normal file
425
workplans/IRP-WP-0001-pipeline-validation.md
Normal file
@@ -0,0 +1,425 @@
|
||||
---
|
||||
id: IRP-WP-0001
|
||||
type: workplan
|
||||
title: "ihp-railiance-probe — Full Pipeline Validation"
|
||||
domain: coulomb
|
||||
repo: ihp-railiance-probe
|
||||
status: todo
|
||||
owner: tegwick
|
||||
created: "2026-05-02"
|
||||
updated: "2026-05-02"
|
||||
---
|
||||
|
||||
# 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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Push the built image to the Gitea container registry:
|
||||
|
||||
```bash
|
||||
sshpass -p 'hcs26!x' ssh root@192.168.178.135 \
|
||||
'cd /root/ihp-railiance-probe && \
|
||||
SHA=$(git rev-parse --short HEAD) && \
|
||||
skopeo copy docker-archive:result \
|
||||
docker://92.205.130.254:32166/coulomb/ihp-railiance-probe:$SHA'
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
skopeo inspect docker://92.205.130.254:32166/coulomb/ihp-railiance-probe:<SHA>
|
||||
```
|
||||
|
||||
**Exit criteria:** `skopeo inspect` succeeds; image visible in Gitea Packages UI.
|
||||
|
||||
---
|
||||
|
||||
### T08 — Helm chart
|
||||
|
||||
```task
|
||||
id: IRP-WP-0001-T08
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
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 | todo |
|
||||
| T02 | `devenv up` → IHP welcome page | todo |
|
||||
| T03 | `probes` table in DB; code-gen passes | todo |
|
||||
| T04 | `/healthz` returns `200 ok` | todo |
|
||||
| T05 | Hspec `test` exits 0 | todo |
|
||||
| T06 | `nix build .#docker` on haskelseed succeeds | todo |
|
||||
| T07 | Image visible in Gitea registry | todo |
|
||||
| T08 | `helm lint chart/` passes | todo |
|
||||
| T09 | k3s can pull from HTTP registry | todo |
|
||||
| T10 | Pod Running on Railiance01 | todo |
|
||||
| T11 | Smoke tests pass; log entry committed | todo |
|
||||
| T12 | CI pipeline automated (optional) | todo |
|
||||
Reference in New Issue
Block a user