Moving the CultLib pin forward to pick up the presence contract also brought
ten unrelated commits, one of which republished the TypeScript packages under
the @gamecult scope. Two things broke on that rename.
The recipe selected build workspaces by package name, so `--workspace=
cultcache-ts` no longer matched anything. Selecting by path instead says what
is actually meant and survives the next rename.
cultnet-ts depends on cultcache-ts by registry range, not by workspace
protocol, so the vendored copy is only used because Heimdall overrides that
name to the vendored path. The override still named the old one, and pnpm went
looking for @gamecult/cultcache-ts on a registry that does not have it.
Heimdall now uses the package's real name in its dependency, its override and
its imports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Idunn refuses to seal a release containing a symlink with parent traversal,
and pnpm's node_modules is exactly that -- a forest of relative links into
.pnpm. Negotiating with that layout failed twice: --node-linker=hoisted was
silently ignored (staging trees still had .pnpm), and pnpm 10's
--config.node-linker=hoisted did not resolve it either.
Bundling removes the question. `pnpm bundle` runs esbuild over src/index.ts
into a single 2.5 MB dist/heimdall with a shebang and a createRequire shim
for the CJS dependencies, pg-native left external as the optional native
binding it is. node_modules is now a build input rather than an artifact.
That also fixes a defect the seal had not reached yet: launch_command does
`ensure!(executable.is_file())` and execs the artifact directly, so the old
recipe -- executable_artifact pointing at the dist *directory*, with
"dist/src/index.js" as the first argument -- would have failed there too.
The service is now one file that is genuinely executable, and its only
arguments are the state root Idunn assigns.
Shipping a dependency tree to run a service was the larger liability
anyway; this deletes it rather than making it sealable.
Recipe validates; 54 pass, 1 skipped, tsc clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Heimdall took CultLib as file:../CultLib/packages/*, a sibling checkout
outside this repository. Nothing pinned which CultLib a given Heimdall
commit was built against, and a sealed source could not reach it. The
deploy manifest worked around this by cloning CultLib separately to
/srv/build/CultLib with cultlib_commit=5cefa0db hardcoded in shell -- the
pin existed, it just lived in an untyped place where no build could see it.
vendor/CultLib is now a submodule pinned at exactly 5cefa0db, the commit
the manifest already used, so this change is purely structural: the
dependency does not move. Bumping to a current CultLib is a separate
change, and should be, or a failure could not be attributed.
This also uncovered that the build was never reproducible from a clean
checkout. The sibling directory had dist/ present because someone had
built it by hand at some point; a fresh CultLib clone has none, and the
packages' main/types point straight at dist/. Building Heimdall against a
fresh CultLib fails with thirteen TS2307s. The manifest knew -- it runs
npm ci then npm run build for cultcache-ts, cultnet-ts and cultmesh-ts
before touching Heimdall -- but that knowledge lived only in the shell
script. It belongs in the recipe's steps, which is where it goes next.
Verified end to end on a fresh submodule clone: the three CultLib packages
build, pnpm install resolves, tsc exits 0, dist/src/index.js is produced
(the exact path heimdall.service runs), and vitest passes 38 tests in 9
files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>