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>
I claimed this morning that cultcache-ts could not read a cultcache-rs
store and that the CultLib forks had diverged. That was false, and it was
false in the usual way: generalised from one file.
cultcache-ts reads Idunn's live control store on yggdrasil directly -- 36
records, no complaint. Ordinary CultCache stores use the same framing in
both runtimes, and that covers the control store, the runtime bundle's
expected.cc and activation.cc, and the process write lease. The exception
is narrow and deliberate: service identity private stores are written by
cultnet-rs's atomic_create_private_store as a single bare positional
envelope, because they are a private-key container rather than a record
store.
The consequence was a live bug. Both readers I wrote decoded the
private-store framing, so the write lease and expected-incarnation readers
would have failed on the first real deployment -- with green tests, because
I had built the fixtures the same wrong way. process-write-lease.ts now
uses the CultCache client, provider-health-identity.ts keeps the bare
reader for the fd-passed identity where it belongs, and the lease fixtures
are written with the client so a test can disagree with me.
Also corrected the write-up in Idunn/docs/migration.md and the memory that
recorded the false finding. Three places had it wrong; leaving any of them
would have taught the next session the same mistake.
54 pass, 1 skipped, 12 files; tsc clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cultcache-ts and cultcache-rs write different single-file store layouts,
and the TypeScript client cannot read the Rust one at all -- its schema
rejects it with "expected object, received array". Verified against the
real enrolled identity on yggdrasil, not inferred.
cultcache-rs [ [key, type, payload, stored_at, schema_id], ... ]
cultcache-ts [ formatVersion, catalog, records ] with named envelopes
Both readers I wrote for Idunn-written files assumed the named-object
shape. They were wrong, and their tests passed only because the fixtures
were built to match the assumption rather than the writer -- the same
failure the census keeps recording, where the check agrees with the guess.
src/idunn-store.ts now owns that decode, consumed by the write-lease reader
and the presence-identity reader, and says plainly that it exists because
the two CultLib forks disagree. If they are reconciled, it should be
deleted in favour of the client.
The write-lease fixture is rebuilt as a positional envelope, and the
descriptor test now wraps a genuine machine-bound payload the way
cultcache-rs writes it instead of enrolling through the TypeScript client
and reading it back.
55 pass, 1 skipped, 12 files; tsc clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
During a deployment two Heimdall processes exist at once under distinct
UIDs and namespaces, and both are configured with the same CultCache store
path. Exactly one may write it. Idunn decides which by publishing a lease
record naming an incarnation; nothing in Heimdall read it, so both
generations would have written heimdall.service.cc.
src/process-write-lease.ts answers one question -- may this process write
state right now -- by reading the lease and the runtime bundle's
expected.cc, and comparing incarnation identity. It grants nothing and
throws nothing outward: every failure is a decision of mayWrite:false with
a reason, because for a warming candidate "not yet my turn" is the
expected condition and crashing would turn it into a failed deployment.
Records are decoded as the canonical positional MessagePack Idunn writes
(14 fields for the lease, schema-checked), and the tests build the same
envelope bytes rather than convenient objects.
publishHeimdallVerseState is the single writer, so that is the only place
gated. Outside Idunn no lease path is configured and writes proceed
unconditionally, which keeps developer runs and the current plain unit
working.
Second defect found while wiring it: health publication hardcoded
state "active". A candidate would have claimed active from its first
pulse, which is both untrue and structurally broken -- Idunn must observe
`warming` presence before it fences the incumbent, so a candidate that
never reports warming cannot be promoted. State is now derived from the
lease: warming until held, active after, with the reason carried in the
detail string.
49 tests across 11 files; tsc clean under exactOptionalPropertyTypes.
The recipe's status banner now records the contract as implemented, with
the honest caveat that none of it has been through a live `idunn up` --
the daemon on yggdrasil is still the previous generation, so the first
deployment remains the real verification.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>