From 34c4179d557b88c675951ef720ce8dd4a2ec08c2 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 21 Sep 2026 11:17:41 -0700 Subject: [PATCH] feat(irv-ml1): provision the Draupnir geometry engine, acceptance-tested Operator-authorized. build123d 0.12.0 + OCP, numpy 2.4.6, trimesh 5.1.0 in a 3.11 venv, FreeCAD 1.0.0 headless as an AppImage, artifact root on smithy. The acceptance test is the point and it PASSES: brokkr-smithy-dev shipped a contrastive control pair whose every property is construction-known, so the install is checked against arithmetic rather than against a render. Both parts build, both bounding boxes are exactly 40x40x5, and both volumes match to float tolerance. These scripts had never been executed by anyone -- build123d is deliberately absent from the authoring box -- so this was their first run. FreeCAD is an AppImage BY CHOICE: the distro package drags a GUI/Qt dependency tree onto a host running twelve GPU and audio services, where an AppImage is self-contained, pinned and removable with one rm. Slicer DEFERRED rather than guessed. PrusaSlicer's latest release ships no Linux AppImage at all, and a guessed release filename had already produced a 4 KB error page in this same session. It is the one item they said could lag. Two things worth keeping in the header: irv-ml1's ssh config pins the gitea host to gitea_brokkr_smithy with IdentitiesOnly, whose comment is irv-ml1-r14-deploy and NOT the arbo key that sorts first in ~/.ssh/*.pub -- I registered the wrong one first, and the failure names the right one. And `ssh irv-ml1` is lkraven with no NOPASSWD sudo while `ssh infra-ops@irv-ml1` has it, so only the artifact-root step needs the other identity. The deploy key was registered with claude-bot's own token rather than the operator's site-admin token -- the org grant from earlier today doing real work on its first use. --- playbooks/irv-ml1-draupnir-engine.yaml | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 playbooks/irv-ml1-draupnir-engine.yaml diff --git a/playbooks/irv-ml1-draupnir-engine.yaml b/playbooks/irv-ml1-draupnir-engine.yaml new file mode 100644 index 0000000..ee2b7c7 --- /dev/null +++ b/playbooks/irv-ml1-draupnir-engine.yaml @@ -0,0 +1,77 @@ +# Provision the Draupnir geometry engine on irv-ml1. +# +# scripts/elway irv-ml1 --playbook playbooks/irv-ml1-draupnir-engine.yaml +# +# Records what was VERIFIED on 2026-09-21, not what was planned. +# +# ⚠ SUDO IDENTITY. `ssh irv-ml1` is lkraven and has NO NOPASSWD sudo; +# `ssh infra-ops@irv-ml1` does. The only step needing root is the artifact +# root, so everything else runs as lkraven, where the ssh config and deploy +# keys live. +# +# ⚠ DEPLOY KEY. irv-ml1's ~/.ssh/config pins gitea.phasefinal.com to +# `gitea_brokkr_smithy` with IdentitiesOnly yes — its comment is +# "irv-ml1-r14-deploy", NOT the arbo key that sorts first in ~/.ssh/*.pub. +# Registering the wrong one fails with a message naming the right one, which is +# the clue. The key is registered read-only on pfi/draupnir (id 8). +# +# ⚠ FreeCAD is an AppImage BY CHOICE. The distro package drags a GUI/Qt +# dependency tree onto a box running twelve GPU and audio services; an AppImage +# is self-contained, pinned, and removable with one rm. Run it as +# `FreeCAD.AppImage --appimage-extract-and-run freecadcmd`. +# +# ⚠ SLICER DEFERRED. PrusaSlicer's latest release ships NO Linux AppImage +# (Windows/macOS only), and guessing at a release filename already produced a +# 4 KB error page here once. Left undone deliberately rather than done blind. +vars: + repo: git@gitea.phasefinal.com:pfi/draupnir.git + checkout: /home/lkraven/development/draupnir + freecad_url: https://github.com/FreeCAD/FreeCAD/releases/download/1.0.0/FreeCAD_1.0.0-conda-Linux-x86_64-py311.AppImage + +steps: + - name: Artifact root on smithy (500 GB budget, per draupnir remote.py) + sudo: true + shell: mkdir -p /mnt/smithy/draupnir && chmod 2775 /mnt/smithy/draupnir + creates: /mnt/smithy/draupnir + + - name: Clone the engine repo + shell: git clone {{ repo }} {{ checkout }} + creates: "{{ checkout }}/.git" + + - name: uv venv on the 3.11 floor + shell: cd {{ checkout }} && uv venv --python 3.11 + creates: "{{ checkout }}/.venv/bin/python" + + - name: Install the engine extra (build123d + OCP + numpy + trimesh) + shell: cd {{ checkout }} && uv pip install -e '.[engine]' + # Idempotent on the INSTALLED package, not on the venv existing. + when: "! {{ checkout }}/.venv/bin/python -c 'import build123d' 2>/dev/null" + + - name: FreeCAD headless AppImage + shell: mkdir -p ~/opt && curl -sL -o ~/opt/FreeCAD.AppImage {{ freecad_url }} && chmod +x ~/opt/FreeCAD.AppImage + creates: /home/lkraven/opt/FreeCAD.AppImage + +verify: + - name: build123d, numpy and trimesh all import + shell: "{{ checkout }}/.venv/bin/python -c 'import build123d, numpy, trimesh, OCP'" + changed_when: "false" + - name: FreeCAD runs headless + shell: ~/opt/FreeCAD.AppImage --appimage-extract-and-run freecadcmd --version + changed_when: "false" + - name: artifact root is writable by the docker group + shell: test -w /mnt/smithy/draupnir + changed_when: "false" + # The real gate. Both control parts must BUILD and both volumes must match + # construction-known arithmetic — not a render, not an opinion. + - name: acceptance — contrastive control pair builds with correct volumes + shell: >- + cd {{ checkout }} && .venv/bin/python -c + "import math,runpy; + g=runpy.run_path('parts/plate-good/model.py')['result']; + t=runpy.run_path('parts/plate-thin-wall/model.py')['result']; + wg=40*40*5-math.pi*5**2*5; wt=40*40*5-math.pi*19**2*5; + assert abs(g.volume-wg)<1e-6*wg, (g.volume,wg); + assert abs(t.volume-wt)<1e-6*wt, (t.volume,wt); + assert (round(g.bounding_box().size.X,6),round(g.bounding_box().size.Z,6))==(40.0,5.0); + print('acceptance OK')" + changed_when: "false"