Recipe optional_environment gains the Joystick client id and secret file; service-contract documents the row lock, rotation fail-closed and the 410/502 answers; the StreamPixels profile names Heimdall as sole holder of provider refresh tokens (F5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
213 lines
8 KiB
TOML
213 lines
8 KiB
TOML
# The runtime contract below is implemented as of 2026-09-06.
|
|
#
|
|
# GAMECULT_IDUNN_CANDIDATE_BIND src/config.ts takes the candidate socket
|
|
# over HOST/PORT, requires loopback and a
|
|
# usable port, and refuses to start unless
|
|
# GC_ACCESS_BASE_URL is set, so an
|
|
# ephemeral port can never become the
|
|
# provider-registered OAuth callback.
|
|
# GAMECULT_IDUNN_PROCESS_WRITE_LEASE src/process-write-lease.ts. The single
|
|
# writer of heimdall.service.cc declines
|
|
# unless the lease names this incarnation.
|
|
# GAMECULT_IDUNN_RUNTIME_BUNDLE read for expected.cc, which is how the
|
|
# process learns which incarnation it is.
|
|
#
|
|
# Health reports `warming` until the lease is held and `active` after, because
|
|
# Idunn must observe warming presence before it fences the incumbent.
|
|
#
|
|
# Still untested against a live Idunn: nothing here has been through an actual
|
|
# `idunn up heimdall`, and the daemon on yggdrasil is still the previous
|
|
# generation. Treat the first deployment as the real verification.
|
|
|
|
schema = "gamecult.idunn.target_declaration.v1"
|
|
target = "heimdall"
|
|
source_stamp_environment = "HEIMDALL_BUILD_COMMIT"
|
|
required_gitlinks = ["vendor/CultLib"]
|
|
|
|
# CultLib's TypeScript packages resolve through main/types to dist/, which a
|
|
# fresh checkout does not have. They must be built before Heimdall's own
|
|
# typecheck can resolve cultcache-ts, cultnet-ts or cultmesh-ts. This used to
|
|
# live in a shell manifest on the host; it belongs here, where the source seal
|
|
# covers it.
|
|
[[steps]]
|
|
id = "install-cultlib"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["npm", "ci", "--ignore-scripts", "--prefix", "vendor/CultLib"]
|
|
|
|
[[steps]]
|
|
id = "build-cultcache-ts"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["npm", "run", "build", "--prefix", "vendor/CultLib", "--workspace=packages/cultcache-ts"]
|
|
|
|
[[steps]]
|
|
id = "build-cultnet-ts"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["npm", "run", "build", "--prefix", "vendor/CultLib", "--workspace=packages/cultnet-ts"]
|
|
|
|
[[steps]]
|
|
id = "build-cultmesh-ts"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["npm", "run", "build", "--prefix", "vendor/CultLib", "--workspace=packages/cultmesh-ts"]
|
|
|
|
[[steps]]
|
|
id = "install-heimdall"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["corepack", "pnpm", "install", "--frozen-lockfile", "--config.node-linker=hoisted"]
|
|
|
|
[[steps]]
|
|
id = "test-heimdall"
|
|
phase = "test"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["corepack", "pnpm", "test"]
|
|
|
|
[[steps]]
|
|
id = "build-heimdall"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["corepack", "pnpm", "build"]
|
|
|
|
# The release is one self-contained executable. node_modules is a build
|
|
# input, not an artifact: pnpm lays it out as symlinks into .pnpm, which
|
|
# Idunn refuses to seal, and shipping a dependency tree to run a service is
|
|
# a larger liability than bundling it.
|
|
[[steps]]
|
|
id = "bundle-heimdall"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["corepack", "pnpm", "bundle"]
|
|
|
|
# The unit runs dist/src/index.js, and Node resolves its imports at runtime, so
|
|
# the release is the compiled output plus the production dependency tree. These
|
|
# are the two artifacts; nothing else from the build tree is installed.
|
|
[[steps]]
|
|
id = "stage-node"
|
|
phase = "build"
|
|
runner = "node-build"
|
|
required_environment = ["NPM_CONFIG_CACHE", "HOME", "COREPACK_HOME"]
|
|
argv = ["node", "-e", "require('fs').copyFileSync(process.execPath, 'dist/node')"]
|
|
|
|
# Idunn compares the running process's executable inode against the sealed
|
|
# artifact, so the executed inode must live inside the release. A shebang script
|
|
# cannot satisfy that: the kernel execs the interpreter, and the process's
|
|
# executable is /usr/bin/node rather than anything sealed. Shipping the runner
|
|
# image's own node as the executable brings the interpreter inside the seal and
|
|
# pins it to the runner image digest, which is a stronger guarantee than
|
|
# depending on whatever node the host happens to have.
|
|
[[artifacts]]
|
|
id = "node"
|
|
source_kind = "runner-output"
|
|
runner = "node-build"
|
|
source = "dist/node"
|
|
destination = "node"
|
|
executable = true
|
|
|
|
[[artifacts]]
|
|
id = "heimdall-bundle"
|
|
source_kind = "runner-output"
|
|
runner = "node-build"
|
|
source = "dist/heimdall"
|
|
destination = "heimdall.mjs"
|
|
executable = false
|
|
|
|
[service]
|
|
executable_artifact = "node"
|
|
arguments = [
|
|
{ kind = "literal", value = "heimdall.mjs" },
|
|
{ kind = "literal", value = "--state-root" },
|
|
{ kind = "binding", name = "state_root" },
|
|
]
|
|
transport = "http"
|
|
route_required = true
|
|
# A secret input is declared only as its `*_FILE` name. The binding binds it
|
|
# under [workload.secret_files] to a root:root 0400 credential file, so the value
|
|
# never sits in the binding, the unit or a plan. Idunn refuses a binding that
|
|
# sets an undeclared name, so a plaintext secret has nowhere to go.
|
|
optional_environment = [
|
|
"CULTMESH_URI_ODIN_RUDP",
|
|
"GC_ACCESS_APP_BIFROST_BACKEND_CALLBACK_URLS",
|
|
"GC_ACCESS_APP_BIFROST_SHARED_SECRET_FILE",
|
|
"GC_ACCESS_APP_GHOSTLIGHT_BACKEND_CALLBACK_URLS",
|
|
"GC_ACCESS_APP_GHOSTLIGHT_RUNTIME_IDS",
|
|
"GC_ACCESS_APP_GHOSTLIGHT_SHARED_SECRET_FILE",
|
|
"GC_ACCESS_APP_REPIXELIZER_SHARED_SECRET_FILE",
|
|
"GC_ACCESS_APP_STREAMPIXELS_SHARED_SECRET_FILE",
|
|
"GC_ACCESS_APPLY_SCHEMA_ON_STARTUP",
|
|
"GC_ACCESS_BIFROST_PATRON_SUPPORT_ENDPOINT",
|
|
"GC_ACCESS_BIFROST_PATRON_SUPPORT_SECRET_FILE",
|
|
"GC_ACCESS_COMPLETION_TTL_SECONDS",
|
|
"GC_ACCESS_IDUNN_HEALTH_CONTRACT",
|
|
"GC_ACCESS_IDUNN_RUDP_HEALTH",
|
|
"GC_ACCESS_ISSUER",
|
|
"GC_ACCESS_ODIN_CULTMESH_URI",
|
|
"GC_ACCESS_PRIVATE_COMMAND_HOST",
|
|
"GC_ACCESS_PRIVATE_COMMAND_PORT",
|
|
"GC_ACCESS_PROVIDER_DISCORD_CLIENT_ID",
|
|
"GC_ACCESS_PROVIDER_DISCORD_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_GITHUB_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_JOYSTICK_CLIENT_ID",
|
|
"GC_ACCESS_PROVIDER_JOYSTICK_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_PATREON_CLIENT_ID",
|
|
"GC_ACCESS_PROVIDER_PATREON_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_SPOTIFY_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_TWITCH_CLIENT_ID",
|
|
"GC_ACCESS_PROVIDER_TWITCH_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_PROVIDER_YOUTUBE_CLIENT_ID",
|
|
"GC_ACCESS_PROVIDER_YOUTUBE_CLIENT_SECRET_FILE",
|
|
"GC_ACCESS_SESSION_TTL_SECONDS",
|
|
"GC_ACCESS_SIGNING_KEY_ID",
|
|
"GC_ACCESS_SIGNING_PRIVATE_KEY_BOOTSTRAP",
|
|
"GC_ACCESS_SIGNING_PRIVATE_KEY_PATH",
|
|
"GC_ACCESS_STATE_TTL_SECONDS",
|
|
"GC_ACCESS_STORAGE_BACKEND",
|
|
"GC_ACCESS_TOKEN_ENCRYPTION_KEY_BASE64_FILE",
|
|
]
|
|
required_environment = [
|
|
"GAMECULT_IDUNN_RUNTIME_BUNDLE",
|
|
"GAMECULT_IDUNN_CANDIDATE_BIND",
|
|
"GAMECULT_IDUNN_PROCESS_WRITE_LEASE",
|
|
"GC_ACCESS_BASE_URL",
|
|
"GC_ACCESS_DATABASE_URL_FILE",
|
|
]
|
|
|
|
[service.health]
|
|
contract = "heimdall.cultnet-rudp-access-health"
|
|
|
|
[state]
|
|
schema_generation = "heimdall-v1"
|
|
|
|
[[state.slots]]
|
|
id = "service"
|
|
relative_path = "heimdall.service.cc"
|
|
kind = "cultcache-file"
|
|
schema = "heimdall.service_state.v1"
|
|
writer = "process-bound-single-writer"
|
|
recovery = "preserve"
|
|
startup = "create-or-open-after-write-lease"
|
|
|
|
[[provides]]
|
|
capability = "gamecult.shared-auth"
|
|
schema = "heimdall.access-discovery.v1"
|
|
compatibility = "v1"
|
|
|
|
# Heimdall publishes its presence to Odin over CultMesh RUDP and never answers
|
|
# an Idunn route challenge, so it declares the Odin dependency: Idunn admits it
|
|
# as Ready on Odin's correlation, not on a challenge it will not answer.
|
|
[[dependencies]]
|
|
kind = "shared-infrastructure"
|
|
capability = "odin.verse-rendezvous"
|
|
schema = "odin.verse-topology.v1"
|
|
compatibility = "v1"
|
|
startup = "before-promotion"
|