Survey heimdall, repixelizer and streampixels: none can take a recipe yet
I called these three "the simple ones" and "compose-shaped". Both were wrong. None of them is compose -- the legacy actuator restarts all three with `docker compose -f /srv/compose/yggdrasil-apps.yaml`, and that file does not exist; /srv/compose holds only odin, voidbot and voidbot-retrieval. The path is guarded, so every restart of these three has been failing at the guard. They are running only because nothing has asked them to restart, which is the difference between a supervised service and one that happens to be up. Each needs a change in its own repository before a recipe means anything, and the changes are different shapes, so this is not one piece of work: streampixels has real build scripts (tsc, next build) and no external dependencies, but its service unit runs TypeScript through tsx from a checkout, and it is two units under one target name. A recipe declares one [service]; the two have different environments, ports, and an ordering dependency. It was one target only because a shell line restarted both. repixelizer has the cleanest dependencies of the three -- no CultLib, no submodules -- but runs a venv interpreter against a checkout, so there is no artifact to seal and no revision observable from the process. heimdall is the most release-shaped and needs the deepest change: it takes CultLib as file:../CultLib, a sibling checkout outside the repository. The current manifest works around that by cloning CultLib separately with cultlib_commit hardcoded in shell -- exactly the untyped authority recipes exist to replace. It needs vendor/CultLib as a gitlink, like Ghostlight's vendor/eve. Also corrects the advice to copy CodexConnector's recipe. That recipe is stale: it declares required_gitlinks for vendor/cultcache-rs and vendor/cultnet-rs, and the repo has no .gitmodules, no vendor/, and takes CultLib as a pinned cargo git dep instead. Recipes drift from their repos silently because nothing re-checks them; run `idunn validate` in each target's CI so drift fails at the repo rather than on the host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c8c1f400e9
commit
a03b36462d
1 changed files with 96 additions and 21 deletions
|
|
@ -41,22 +41,14 @@ Migrate in dependency order, and migrate the authority's own dependencies last:
|
|||
resident to produce frames nothing rendered. The only reference to
|
||||
`gjallar.overview` in the estate is one Odin test file. Stopped and disabled;
|
||||
420 MB under `/srv/gjallar` is reclaimable.
|
||||
1. **`heimdall`** — first. It is the only target already shaped like a release:
|
||||
`node /srv/heimdall/app/current/dist/src/index.js`, with a `current` symlink
|
||||
over a built `dist/`. That is what `release_root` and the workload driver
|
||||
expect, so it is the shortest distance between what runs and what a recipe
|
||||
describes.
|
||||
2. **`repixelizer`** — second. Unit is `repixelizer-gui` (the target name and
|
||||
the unit name differ). Runs `/srv/repixelizer/.venv/bin/python` against a
|
||||
source checkout at `/srv/repixelizer/app`, so the recipe needs a real build
|
||||
step producing an artifact rather than a checkout to point at.
|
||||
3. **`streampixels`** — third, and the hardest of the three despite looking
|
||||
simple. It is **two units** under one target name, `streampixels-service` and
|
||||
`streampixels-web`, and the service one runs
|
||||
`node node_modules/tsx/dist/cli.mjs apps/service/src/index.ts` — raw
|
||||
TypeScript transpiled at startup, from a checkout, with no build artifact to
|
||||
seal. Idunn's model is sealed exact source *and* artifacts; this target has
|
||||
to grow a build before it can have a recipe.
|
||||
1. **`streampixels`** — first of the three. See *The first three targets*: it
|
||||
already has real build scripts and no external dependencies, but must split
|
||||
into two Idunn targets and stop running TypeScript through `tsx`.
|
||||
2. **`repixelizer`** — second. No CultLib dependency at all; needs an artifact
|
||||
story for Python before a recipe means anything. Unit is `repixelizer-gui`.
|
||||
3. **`heimdall`** — third, not first. Most release-shaped, but takes CultLib
|
||||
from a sibling checkout via `file:../CultLib`, so it needs a `vendor/CultLib`
|
||||
gitlink before its source can be sealed.
|
||||
|
||||
**Their continuity is already broken, and has been.** The legacy actuator
|
||||
restarts all three with
|
||||
|
|
@ -107,11 +99,20 @@ reaches for host authority is rejected by `admit()` rather than honoured.
|
|||
variants — eight in total. So recipe authoring is *not* the bottleneck for those
|
||||
targets; the missing halves are the operator bindings and the daemon cutover.
|
||||
|
||||
Read `CodexConnector/deployment/idunn/recipe.toml` before writing a new one. It
|
||||
is the richest worked example — it carries `[[external_inputs]]` pinned by
|
||||
sha256, a non-Rust runner, and a typed credential-store schema — and since that
|
||||
target is being retired rather than migrated, it is a reference with no live
|
||||
claim on it. `Odin`'s is the minimal example.
|
||||
`Odin/deployment/idunn/recipe.toml` is the reference. It is minimal, and
|
||||
`idunn validate` accepts it.
|
||||
|
||||
**Do not copy `CodexConnector`'s.** It is the richest-looking example and it is
|
||||
**stale**: it declares `required_gitlinks = ["vendor/cultcache-rs",
|
||||
"vendor/cultnet-rs"]`, but that repository has no `.gitmodules`, no `vendor/`,
|
||||
and now takes CultLib as a pinned cargo git dependency. The recipe describes a
|
||||
repository shape that stopped existing.
|
||||
|
||||
That drift is the general hazard, not a CodexConnector quirk: a recipe lives in
|
||||
the target's repo and nothing re-checks it when the repo changes underneath.
|
||||
`idunn validate --recipe` catches malformed recipes; it does not yet catch a
|
||||
recipe whose declared gitlinks are absent from the tree. Run it in each target's
|
||||
CI so drift fails at the repo, not on the host.
|
||||
|
||||
### 3. Write the binding, on the host
|
||||
|
||||
|
|
@ -163,6 +164,80 @@ silently drop a `*` from a neighbouring grant and break a live target's deploy
|
|||
path — as happened here on 2026-09-06 to `deploy streampixels`, caught by
|
||||
diffing and not by reading.
|
||||
|
||||
## The first three targets, and what each needs first
|
||||
|
||||
Surveyed 2026-09-06. **None of the three can take a recipe as they stand.** Each
|
||||
needs a change in its own repository first, and the changes are not the same
|
||||
shape, so they are not one piece of work. Ordered by how much has to happen.
|
||||
|
||||
### `streampixels` — split one target into two, then use the build it already has
|
||||
|
||||
Both apps already have build scripts: `apps/service` runs `tsc -p
|
||||
tsconfig.json`, `apps/web` runs `next build`, and the root has `pnpm -r build`.
|
||||
No submodules, no `file:` dependencies. The obstacle is not the build.
|
||||
|
||||
- **The service does not use its own build.** The unit runs
|
||||
`node node_modules/tsx/dist/cli.mjs apps/service/src/index.ts` — TypeScript
|
||||
transpiled at startup from a checkout at `/srv/streampixels/app`. Idunn seals
|
||||
artifacts; there is nothing sealed about a source tree plus a transpiler. Point
|
||||
the unit at the `tsc` output.
|
||||
- **One legacy target is two Idunn targets.** A recipe declares a single
|
||||
`[service]`, and a binding a single `unit_prefix`. `streampixels-service` and
|
||||
`streampixels-web` have different environments, different ports, and an
|
||||
ordering dependency (`web` is `After=` `service`). They were one target only
|
||||
because the shell actuator restarted both with one `docker compose` line.
|
||||
Split them; the dependency belongs in the recipe's `[[provides]]`/dependency
|
||||
surface, not in a shared name.
|
||||
- Runtime artifact question to settle: a Node service's release is `dist/` plus
|
||||
its production `node_modules`, unless it is bundled. Decide which before
|
||||
writing `[[artifacts]]`.
|
||||
|
||||
### `repixelizer` — needs an artifact story for Python
|
||||
|
||||
Simplest dependency story of the three: `pyproject.toml`, no CultLib dependency,
|
||||
no submodules. But it runs `/srv/repixelizer/.venv/bin/python
|
||||
/srv/repixelizer/app/scripts/run_gui.py` — an interpreter from a venv against a
|
||||
checkout, so there is no artifact to seal and no revision observable from the
|
||||
process. It needs a build step producing something exact (a wheel, or a venv
|
||||
materialized into the release root) before a recipe means anything.
|
||||
|
||||
Note also the name mismatch: the target is `repixelizer`, the unit is
|
||||
`repixelizer-gui`.
|
||||
|
||||
### `heimdall` — needs CultLib as a gitlink
|
||||
|
||||
Ironically the most release-shaped of the three and the one needing the deepest
|
||||
change. It already deploys to `/srv/heimdall/app/releases/<sha>/app` behind a
|
||||
`current` symlink, which is exactly what `release_root` expects, and it builds
|
||||
with `tsc` to `dist/`.
|
||||
|
||||
But its `package.json` takes CultLib as
|
||||
`"cultcache-ts": "file:../CultLib/packages/cultcache-ts"` — a **sibling checkout
|
||||
outside the repository**. A sealed source cannot reach it, and no revision of
|
||||
Heimdall pins which CultLib was used. The current deploy manifest works around
|
||||
this by cloning CultLib separately and hardcoding `cultlib_commit=5cefa0db...`
|
||||
in shell, which is precisely the untyped authority the recipe model exists to
|
||||
replace.
|
||||
|
||||
The estate's answer is a vendor submodule: Ghostlight has `vendor/eve`, and
|
||||
`required_gitlinks` plus the binding's `[repository.gitlinks]` is how a recipe
|
||||
declares one. Heimdall needs `vendor/CultLib` as a gitlink and its `file:`
|
||||
dependencies repointed at it. Then the CultLib revision is pinned by the
|
||||
Heimdall commit, and the seal covers both.
|
||||
|
||||
Do this one last of the three, and expect it to be a real change to how Heimdall
|
||||
builds rather than a deployment edit.
|
||||
|
||||
### What this survey says generally
|
||||
|
||||
Every one of these targets runs from a checkout or an unpinned sibling, and each
|
||||
was fine as long as a shell script did the deploying, because a shell script can
|
||||
just `cd` somewhere. The recipe model asks a question the old one never did —
|
||||
*what exactly is this release, and can you name it?* — and for three of eleven
|
||||
targets the honest answer today is no. That is the migration's real cost, and it
|
||||
is worth paying: it is the same question as "is everyone on main and current
|
||||
with each other", asked where it can be enforced.
|
||||
|
||||
## Why retirement forces the migration
|
||||
|
||||
The previous generation **cannot express that a target is retired.** Its target
|
||||
|
|
|
|||
Loading…
Reference in a new issue