Results of the login-node half of the layer-7 spike (CLAUDE.md "Pending the one-time Perlmutter spike"), run on login37, 2026-08-21. The allocation half (sbatch materialize, multi-node) is still pending — list at the bottom.
Host: SLES 15-SP6, kernel 6.4.0-cray_shasta, x86_64 · podman-hpc 1.2.2 wrapping podman 5.8.2, no docker · uv self-installed in ~/.local/bin · git-annex wheel 10.20260717 · $SCRATCH = Lustre, $HOME = GPFS.
Spike questions answered
Landlock is not in the SLES boot LSM list
/sys/kernel/security/lsm → lockdown,capability,selinux,bpf. Direct landlock_create_ruleset syscall returns ENOTSUP. The handled branch works exactly as designed: lc run executes, prints the downgrade note, lc status shows sandbox: none, runs record fs: open. So on Perlmutter direct mode is unenforced and containerized mode is the only real boundary — worth saying in the user docs' NERSC material.
One message nit: the note's parenthetical says "kernel < 5.13, blocked by seccomp, or unsupported arch" — none of which is the cause here (kernel 6.4; the LSM simply isn't in the boot lsm= list). A fourth clause would make the note truthful on the machine most likely to show it. Compute nodes may boot a different image; re-check there.
podman-hpc migrate: bare id accepted, re-run is a no-op
podman-hpc migrate <bare-image-id> exits 0 in 0.55 s, and a second invocation is the same 0.55 s no-op. runtime_for_run's unconditional migrate is validated — no guard needed.
--module site mounts vs fs: declared: honest by default, one leak vector
All 13 site modules (/etc/podman_hpc/modules.d/) are opt-in, each gated on a CLI flag or an env var: MOUNT_HOME, MOUNT_SCRATCH, MOUNT_CFS, MOUNT_JUPYTER, ENABLE_GPU, ENABLE_MPICH_SS, ENABLE_NCCL*, ENABLE_OPENMPI_*, ENABLE_CUDA_MPICH_SS, ENABLE_CVMFS. A bare podman-hpc run injects no site mounts (verified: /proc/mounts inside the container shows nothing beyond standard plumbing), and nothing in /etc/profile.d sets the gate variables. So fs: declared is honest out of the box.
The leak vector is real, though: MOUNT_SCRATCH=1 podman-hpc run … silently bind-mounts $SCRATCH into the container (verified) — and lc passes the driver's ambient environment to the runtime wrapper (the container env allowlist doesn't cover the wrapper's own env). A user with export MOUNT_HOME=1 in their dotfiles gets $HOME inside every recipe container while the manifest attests fs: declared. This is the recorded "one item that could add a flag", now with a concrete shape. Tension to weigh before deciding: ENABLE_GPU/ENABLE_MPICH_SS are the legitimate site mechanism for GPU/MPI recipes under podman-hpc, so a blanket scrub (the UV_* treatment) would close the honesty hole and break GPU recipes with it. Options seem to be: scrub MOUNT_* only; attest the gate variables that were set; or a declaration surface for modules. Not decided here.
git-annex on Lustre vs GPFS: throughput fine, small-file add is the cost
200 MB file + 500×16 KB files, template .gitattributes, annex.thin=true add — the exact operations lc pays:
| operation |
$SCRATCH (Lustre) |
$HOME (GPFS) |
git add 200 MB (filter=annex, thin) |
1.9 s |
2.1 s |
git add 500 × 16 KB |
25.4 s (~50 ms/file) |
8.6 s |
| commit |
2.2 s |
1.7 s |
git annex get 200 MB (same-fs origin) |
4.5 s |
3.1 s |
git annex get 500 small |
1.0 s |
0.6 s |
Thin hard links behave normally on Lustre (link count 2, object store not doubled) — the "does Lustre make the hard link behave differently" question is answered: no. The real cost is per-file metadata latency on add (wall 25 s vs ~8 s CPU): an output of many small files pays ~50 ms each on $SCRATCH. One output = one file stays cheap; sharded outputs (thousands of files) are where this bites. Nothing to design around yet.
nidXXXXXX resolution — partial (login-node) answer
nid200001 / nid001000 resolve via site DNS from the login node (to .chn.perlmutter addresses). Whether a peer compute node resolves and can reach SLURMD_NODENAME's address (vs needing --interface hsn0) still needs the allocation.
Bugs found
-
tests/test_container_smoke.py::_inspect_id KeyError: 'podman-hpc' — the helper's argv dict has only podman/docker keys, but the suite parametrizes over podman-hpc. Fails test_build_commits…[podman-hpc] and test_materialize_end_to_end…[podman-hpc] at their final assert. Everything before the helper passed — see below. podman-hpc wraps podman, so ["podman-hpc", "image", "inspect", …] would work; the fix is treating the podman family positively, as container._PODMAN_FAMILY does. This bug is only reachable on a host with podman-hpc installed — which is exactly why the spike says running the suite here is the spike.
-
Bare podman cannot run containers on a login node — podman run --userns=keep-id fails at container creation: creating an ID-mapped copy of layer … chown …/.local/share/containers/storage/…: operation not permitted, with podman's own warning Network file system detected as backing store ($HOME is GPFS). Build works; run does not. This is precisely why NERSC ships podman-hpc, and detection order (podman-hpc → podman) means real lc usage never hits it. Consequence for the suite: test_the_probe_and_its_boundary[podman] and test_materialize_end_to_end…[podman] fail on this host, so LC_CONTAINER_TESTS_REQUIRED=1 cannot go fully green on a NERSC login node. Worth considering a skip (with reason) for the bare-podman parametrization when its graph root sits on a network filesystem — podman's warning string shows it can be probed.
-
Minor, template/docs: the scaffolded pyproject.toml pins [tool.uv] required-version = ">=0.12"; NERSC users self-install uv (per docs/user/install.md), and a stale one (0.11.8 here) fails lc init with uv's own clear uv self update message. Works as designed, just recording that the failure mode is real on this site (fixed here by updating; uv is now 0.12.5).
What works end-to-end on a login node
lc init on $SCRATCH: 1.2 s, converges clean on Lustre.
- Login guard:
lc materialize refuses (exit 1) with the exact salloc/sbatch remedies; the rerun entry point (python -m lightcone.engine.worker …) refuses too (exit 2). lc status / lc materialize --check / lc run all work here, as documented.
lc run (direct mode): 0.5 s round trip on $SCRATCH, correct downgrade note.
lc build via podman-hpc: real project on $SCRATCH, [tool.lightcone.image] with apt-install — build, docker-archive save, annex commit (225 MB, thin: link count 2) in 11 s warm. Detection picked podman-hpc over podman correctly with NERSC_HOST set, no fixtures.
- Containerized
lc run: executes in-image, ~9 s including the in-container env converge. lc status header: sandbox: podman-hpc (fs: declared, network: allowed).
- Smoke suite under podman-hpc:
test_the_probe_and_its_boundary[podman-hpc] passed; test_a_rerun_on_a_clone_fetches_the_archive_and_reproduces[podman-hpc] passed — a real datalad rerun on a bytes-free clone fetched the archive through the annex and reproduced, on the site runtime. The e2e materialize ran a real Dask cluster in-image and wrote a manifest asserting mechanism: podman-hpc, fs: declared before dying in the helper bug above.
- Full suite baseline:
564 passed, 27 skipped, 4 failed in 7:42 — the 4 failures are exactly the two bugs above (2× the _inspect_id helper under podman-hpc, 2× bare podman's network-fs store). Everything else is green on this host, including the venue suite's real worker-process path and the crate smoke against the real validator; the sandbox enforcement suite skips as designed (no mechanism, LC_SANDBOX_TESTS_REQUIRED unset).
Still needs the allocation
srun --overlap / --cpus-per-task behavior inside salloc/sbatch steps
SLURMD_NODENAME reachability from peer nodes (else --interface hsn0)
SLURM_CPUS_ON_NODE on a CPU node (128 vs 256 hyperthreads)
- cold-Lustre
distributed import vs the 120 s worker wait (warm import: 0.9 s)
- one real
lc materialize through sbatch, then multi-node containerized materialize (the podman-hpc shared-store claim in anger)
- compute-node checks of the two host facts above: the boot LSM list, and whether any prolog sets
MOUNT_* there
Residue left on the machine: uv upgraded 0.11.8→0.12.5 (~/.local/bin), and the smoke images in the per-user podman-hpc stores (/images/$UID_hpc, $SCRATCH/storage — ~235 MB squash). All probe repos/projects were deleted.
Results of the login-node half of the layer-7 spike (CLAUDE.md "Pending the one-time Perlmutter spike"), run on
login37, 2026-08-21. The allocation half (sbatch materialize, multi-node) is still pending — list at the bottom.Host: SLES 15-SP6, kernel 6.4.0-cray_shasta, x86_64 · podman-hpc 1.2.2 wrapping podman 5.8.2, no docker · uv self-installed in
~/.local/bin· git-annex wheel 10.20260717 ·$SCRATCH= Lustre,$HOME= GPFS.Spike questions answered
Landlock is not in the SLES boot LSM list
/sys/kernel/security/lsm→lockdown,capability,selinux,bpf. Directlandlock_create_rulesetsyscall returns ENOTSUP. The handled branch works exactly as designed:lc runexecutes, prints the downgrade note,lc statusshowssandbox: none, runs recordfs: open. So on Perlmutter direct mode is unenforced and containerized mode is the only real boundary — worth saying in the user docs' NERSC material.One message nit: the note's parenthetical says "kernel < 5.13, blocked by seccomp, or unsupported arch" — none of which is the cause here (kernel 6.4; the LSM simply isn't in the boot
lsm=list). A fourth clause would make the note truthful on the machine most likely to show it. Compute nodes may boot a different image; re-check there.podman-hpc migrate: bare id accepted, re-run is a no-oppodman-hpc migrate <bare-image-id>exits 0 in 0.55 s, and a second invocation is the same 0.55 s no-op.runtime_for_run's unconditional migrate is validated — no guard needed.--modulesite mounts vsfs: declared: honest by default, one leak vectorAll 13 site modules (
/etc/podman_hpc/modules.d/) are opt-in, each gated on a CLI flag or an env var:MOUNT_HOME,MOUNT_SCRATCH,MOUNT_CFS,MOUNT_JUPYTER,ENABLE_GPU,ENABLE_MPICH_SS,ENABLE_NCCL*,ENABLE_OPENMPI_*,ENABLE_CUDA_MPICH_SS,ENABLE_CVMFS. A barepodman-hpc runinjects no site mounts (verified:/proc/mountsinside the container shows nothing beyond standard plumbing), and nothing in/etc/profile.dsets the gate variables. Sofs: declaredis honest out of the box.The leak vector is real, though:
MOUNT_SCRATCH=1 podman-hpc run …silently bind-mounts$SCRATCHinto the container (verified) — and lc passes the driver's ambient environment to the runtime wrapper (the container env allowlist doesn't cover the wrapper's own env). A user withexport MOUNT_HOME=1in their dotfiles gets$HOMEinside every recipe container while the manifest attestsfs: declared. This is the recorded "one item that could add a flag", now with a concrete shape. Tension to weigh before deciding:ENABLE_GPU/ENABLE_MPICH_SSare the legitimate site mechanism for GPU/MPI recipes under podman-hpc, so a blanket scrub (theUV_*treatment) would close the honesty hole and break GPU recipes with it. Options seem to be: scrubMOUNT_*only; attest the gate variables that were set; or a declaration surface for modules. Not decided here.git-annex on Lustre vs GPFS: throughput fine, small-file add is the cost
200 MB file + 500×16 KB files, template
.gitattributes,annex.thin=trueadd — the exact operations lc pays:git add200 MB (filter=annex, thin)git add500 × 16 KBgit annex get200 MB (same-fs origin)git annex get500 smallThin hard links behave normally on Lustre (link count 2, object store not doubled) — the "does Lustre make the hard link behave differently" question is answered: no. The real cost is per-file metadata latency on add (wall 25 s vs ~8 s CPU): an output of many small files pays ~50 ms each on
$SCRATCH. One output = one file stays cheap; sharded outputs (thousands of files) are where this bites. Nothing to design around yet.nidXXXXXXresolution — partial (login-node) answernid200001/nid001000resolve via site DNS from the login node (to.chn.perlmutteraddresses). Whether a peer compute node resolves and can reachSLURMD_NODENAME's address (vs needing--interface hsn0) still needs the allocation.Bugs found
tests/test_container_smoke.py::_inspect_idKeyError:'podman-hpc'— the helper's argv dict has onlypodman/dockerkeys, but the suite parametrizes over podman-hpc. Failstest_build_commits…[podman-hpc]andtest_materialize_end_to_end…[podman-hpc]at their final assert. Everything before the helper passed — see below. podman-hpc wraps podman, so["podman-hpc", "image", "inspect", …]would work; the fix is treating the podman family positively, ascontainer._PODMAN_FAMILYdoes. This bug is only reachable on a host with podman-hpc installed — which is exactly why the spike says running the suite here is the spike.Bare podman cannot run containers on a login node —
podman run --userns=keep-idfails at container creation:creating an ID-mapped copy of layer … chown …/.local/share/containers/storage/…: operation not permitted, with podman's own warningNetwork file system detected as backing store($HOME is GPFS). Build works; run does not. This is precisely why NERSC ships podman-hpc, and detection order (podman-hpc → podman) means real lc usage never hits it. Consequence for the suite:test_the_probe_and_its_boundary[podman]andtest_materialize_end_to_end…[podman]fail on this host, soLC_CONTAINER_TESTS_REQUIRED=1cannot go fully green on a NERSC login node. Worth considering a skip (with reason) for the bare-podman parametrization when its graph root sits on a network filesystem — podman's warning string shows it can be probed.Minor, template/docs: the scaffolded
pyproject.tomlpins[tool.uv] required-version = ">=0.12"; NERSC users self-install uv (perdocs/user/install.md), and a stale one (0.11.8 here) failslc initwith uv's own clearuv self updatemessage. Works as designed, just recording that the failure mode is real on this site (fixed here by updating; uv is now 0.12.5).What works end-to-end on a login node
lc initon$SCRATCH: 1.2 s, converges clean on Lustre.lc materializerefuses (exit 1) with the exactsalloc/sbatchremedies; the rerun entry point (python -m lightcone.engine.worker …) refuses too (exit 2).lc status/lc materialize --check/lc runall work here, as documented.lc run(direct mode): 0.5 s round trip on$SCRATCH, correct downgrade note.lc buildvia podman-hpc: real project on$SCRATCH,[tool.lightcone.image]withapt-install— build,docker-archivesave, annex commit (225 MB, thin: link count 2) in 11 s warm. Detection picked podman-hpc over podman correctly withNERSC_HOSTset, no fixtures.lc run: executes in-image, ~9 s including the in-container env converge.lc statusheader:sandbox: podman-hpc (fs: declared, network: allowed).test_the_probe_and_its_boundary[podman-hpc]passed;test_a_rerun_on_a_clone_fetches_the_archive_and_reproduces[podman-hpc]passed — a realdatalad rerunon a bytes-free clone fetched the archive through the annex and reproduced, on the site runtime. The e2e materialize ran a real Dask cluster in-image and wrote a manifest assertingmechanism: podman-hpc,fs: declaredbefore dying in the helper bug above.564 passed, 27 skipped, 4 failedin 7:42 — the 4 failures are exactly the two bugs above (2× the_inspect_idhelper under podman-hpc, 2× bare podman's network-fs store). Everything else is green on this host, including the venue suite's real worker-process path and the crate smoke against the real validator; the sandbox enforcement suite skips as designed (no mechanism,LC_SANDBOX_TESTS_REQUIREDunset).Still needs the allocation
srun --overlap/--cpus-per-taskbehavior inside salloc/sbatch stepsSLURMD_NODENAMEreachability from peer nodes (else--interface hsn0)SLURM_CPUS_ON_NODEon a CPU node (128 vs 256 hyperthreads)distributedimport vs the 120 s worker wait (warm import: 0.9 s)lc materializethroughsbatch, then multi-node containerized materialize (the podman-hpc shared-store claim in anger)MOUNT_*thereResidue left on the machine: uv upgraded 0.11.8→0.12.5 (
~/.local/bin), and the smoke images in the per-user podman-hpc stores (/images/$UID_hpc,$SCRATCH/storage— ~235 MB squash). All probe repos/projects were deleted.